问题
1.这个恶意代码向磁盘释放了什么?
解答: 我们刚刚完成了Windows
内核病毒的分析,包括了一些过时的RootKit
技术的分析,现在我们回归二进制分析
这里要分析的文件是Lab11-1
这个文件,我们先做一些基本的静态分析
我们先看KERNEL32.DLL
这个导出DLL
我们可以看到这里有个我们需要注意的CreateFileA
导出函数,还有下面那个ExitProcess
函数,我们找找看有没有CreateProcess
这个函数,找了一圈没有
然后我们再看ADVAPI32.DLL
这里有个RegCreateKeyExA
这个东西,还有下面的RegSetValueExA
这个函数,都会改变注册表
然后我们看下一个DLL
,是ADVAPI32.DLL
下面的KERNEL32.DLL
里面的导出函数
这里我们注意到这么几个函数比较有意思
第一是CreateMutexW
,这个函数会创建了一个互斥量,然后就是CreateThread
,这个会创建一个线程,然后就是DeleteFileW
,这个删除一个文件,然后就是我们内核分析时候见过的DeviceIoControl
这个,用于给内核中的驱动发送一个信号的函数
下面的导出函数也证明了这个函数可能会操纵Mutex
和Thread
这里我们发现了MoveFileW
和OpenMutexW
这个函数
这里还有一个SizeofResource
这个函数,说明这个函数对资源节会有一些操作,然后我们还看见了ls*
的一堆对字符串进行操作的函数
我们还可以在ADVAPI32.DLL
下面发现这么两个DLL
其中一个是WINTRUST.DLL
,另一个是SECUR32.DLL
,其中第一个DLL
的作用是
wintrust.dll是 DLL文件信息,用于验证第三方应用程序的文件,目录,内存使用,数据签名等
全名是Microsoft Trust Verification APIs
而下面的SECUR32.DLL
全名是Security Support Provider Interface
,这是一个提供了各种安全支持提供程序的接口调用库
然后我们可以看看这个程序的字符串有什么
我们可以发现这个GinaDLL
和msgina32.dll
字符串中的显示,然后我们开始看资源节的内容
我们可以看到这个.rsrc
节中,有个叫BINARY TGAD 0000
的二进制文件,我们点开就可以发现那个This program cannot be run in DOS mode
这个字符串,这表明这个二进制是个可执行程序
进行完这些基本的静态分析,我们开始使用动态分析,还是先设置DNSfake
和Webfake
,之后使用procmon
设置过滤器来看程序的操作
这里书上有个错误,中文版的书上写的是
设置
Lab11-02.exe
中文版我没有PDF
,所以这里截个英文版的
这里对照了一下英文版的,是Lab11-01.exe
,这里应该是译者的翻译错误
这里在DNS
中病未看见程序查找了什么域名,但是注意这不代表这个程序不会进行网络连接,如果有奇葩的病毒直接用ip
来访问的话,是不会进行任何的DNS
查询的
然后我们再设置一个过滤条件为CreateFile
这个东西,之后我们就可以看见
这里我们看见在本地的目录下创建了一个msgina32.dll
这个动态链接库
这里我们看见创建了一个msgina32.dll
文件
然后我们回到时间顺序,然后看
这里我们可以看到这里调用了一个RegCreateKey
然后创建了一个HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
然后通过函数RegSetValue
来设置了一个值为HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\GinaDLL
而对这个msgina32.dll
的操作只有如下的几个操作
只有一个CreateFile
之后就是WriteFile
,然后就CloseFile
了
我们可以还可以发现这么两个调用CreateFile
的参数的比较有意思,其中一个是conime.exe
,还有一个是apphelp.dll
,其中conime.exe
是控制输入法的程序,apphelp.dll
是维护程序运行时候的一些错误的修正补丁
现在我们将资源节里面的二进制导出来看看和这个msgina32.dll
是不是一样的
这个Out.dll
是我们从资源节导出来的文件
我们可以看到这两个dll
大小是一样的,当然,这样比较是比较肤浅的,比较严谨的比较方式是计算其MD5
和SHA1
的值,我们先在也计算一下
这里我们介绍的是在Linux
下面计算的方法,Windows
的有专门的软件,或者用在线的工具
我这里有台CentOS 7
的机器用做ftp
,直接上传然后计算就好了,因为在Linux
里面自带了计算MD5
的工具
然后就计算MD5
可以看到我们的两个文件的MD5
值是一样的
然后我们计算SHA1
的值
都是一样的
所以我们就可以确定这个在资源节的二进制文件就是msgina32.dll
这个文件
接下来还是一样的,我们打开IDA
来分析这个Lab11-01.exe
,一样的,我们每次遇到call
就做一段分析
然后我们遇到的第一个call
是GetModuleHandleA
,这个函数在MSDN
中定义是
检索指定模块的模块句柄。 该模块必须已由调用进程加载
然后我们可以看到这个函数的入参是0
也就是NULL
如果此参数为NULL,则GetModuleHandle将返回用于创建调用进程(.exe文件)的文件的句柄
这句话的意思就是,如果这个入参是0
,那么这个函数返回的是这个函数的本身的句柄
所以我们这里的返回值是指向这个exe
文件的句柄,然后我们开始看下一个call
我们可以看到这里压入了一个参数eax
,而这个eax
虽然经过上面那么多的变换,还是GetModuleHandleA
的返回值
进来这个函数之后,我们可以看到如下的初始化操作
注意这里的cmp
指令,这里的指令让[ebp+hModule]
与0
进行了比较,我们注意这里的hModule
的偏移值是dword ptr 8
,也就是最后这个的值是[ebp+8]
,根据我们以前的栈分析,ebp+8
的值代表了调用函数的最后一个入参,如果只有一个入参的话,就代表了那个入参的值
当然你这里也可以不用像我们这样分析栈的组成,因为IDA
已经给你标出来了这个值是hModule
OK
,言归正传之后,我们开始分析这段代码
汇编代码到push edi
之前的代码都是在初始化栈
之后的代码
mov [ebp+hResInfo], 0
mov [ebp+hResData], 0
mov [ebp+var_8], 0
mov [ebp+dwSize], 0
mov [ebp+var_C], 0
cmp [ebp+hModule], 0
jnz shrt loc_4010B8
前面的mov
指令都是在为参数赋值,这里将hResInfo
等等的参数都赋值为了0
然后下面就是比较hModule
的值是否为0
,如果函数调用成功的话,返回的是一个指针肯定不等于0
,如果函数调用失败,就返回NULL
也就是0
了
如果这个返回的指针为0
,则cmp
指令之后,ZF
=1
,jnz
不跳转,执行红色的线
如果返回指针不为0
,cmp
之后,ZF
=0
,jnz
跳转,走绿色的线
绿色的线就是
下面就会执行这个FindResourceA
函数,而这个函数在MSDN
中的定义是
确定指定模块中具有指定类型和名称的资源的位置
这里我们注意这个函数的入参,通过查看就可以得出如下的入参表
HRSRC WINAPI FindResource(
_In_opt_ HMODULE hModule = [ebp+hModule],
_In_ LPCTSTR lpName = TGAD,
_In_ LPCTSTR lpType = BINARY
);
这个函数的意思就是将在资源节中名字叫TGAD
,类型是BINARY
的节找出来,函数执行成功之后,将返回一个指定资源的信息块的句柄
下一个函数是LoadResource
这个函数,在MSDN
中的定义是
检索可用于获取指向内存中指定资源的第一个字节的指针的句柄
这个函数调用成功之后,会返回
如果函数成功,则返回值是与资源关联的数据的句柄
这个返回值最后会存在hResData
里面
之后下一个调用是LockResource
这个函数在MSDN
里面的意思就是
检索指向内存中指定资源的指针
这个函数的返回值如下
如果加载的资源可用,则返回值是指向资源第一个字节的指针;否则,它是NULL
这就会把指针开始指向了资源的第一个字节,从这个时候开始才开始操作资源节的内容
然后下一个代码块
这个函数会返回资源节的大小,还是一样的做了一个结果比较跳转
之后调用VirtualAlloc
分配一个空间给要导出的DLL
其中,这个函数在MSDN
中的定义如下
LPVOID WINAPI VirtualAlloc(
_In_opt_ LPVOID lpAddress = 0,
_In_ SIZE_T dwSize = dwSize,
_In_ DWORD flAllocationType = 1000h,
_In_ DWORD flProtect = 4
);
其中需要我们注意的是flAlloctaionType
这个参数,这个参数的值为1000h
在MSDN
里面代表了MEM_COMMIT
意思是
为指定的保留内存页分配内存费用(从内存的总大小和磁盘上的分页文件)。 该函数还保证当调用者最初访问内存时,内容将为零。 除非实际访问虚拟地址,否则不会分配实际的物理页面。
要一步保留并提交页面,请使用MEM_COMMIT |调用VirtualAllocMEM_RESERVE。
尝试通过指定MEM_COMMIT而不使用MEM_RESERVE来提交特定地址范围,除非整个范围已被保留,否则非空lpAddress将失败。 由此产生的错误代码是ERROR_INVALID_ADDRESS。
尝试提交已提交的页面不会导致函数失败。 这意味着您可以在不首先确定每个页面的当前承诺状态的情况下提交页面。
如果lpAddress指定飞地内的地址,则flAllocationType必须是MEM_COMMIT
这一步就是分配了一个空间给将来要释放出来的dll
的
之后的代码,我们可以看到这里调用了_fopen
函数,并且这个函数的mode
是wb
,也就是二进制写入
,名字是msgina32.dll
之后用_fwrite
函数将指向资源节的数据写入了这个_fopen
打开的文件里面,之后就调用了_fclose
关闭句柄,之后还有一个函数叫sub_401299
,起始这个是printf
函数,以前我们分析过为什么是printf
函数
这里就是调用printf
函数输出了一个DR\n
之后用FreeResource
来释放这个操作Resource
的指针
之后我们看这个sub_401000
函数的操作
这是一个在整个函数底部的函数
进入这个函数之后我们可以看见,第一个函数调用是RegCreateKeyExA
这个函数,我们可以得到这个函数在MSDN
中的定义
LONG WINAPI RegCreateKeyEx(
_In_ HKEY hKey = 80000002h,
_In_ LPCTSTR lpSubKey = SubKey,
_Reserved_ DWORD Reserved = 0,
_In_opt_ LPTSTR lpClass = 0,
_In_ DWORD dwOptions = 0,
_In_ REGSAM samDesired = 0F003Fh,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes = 0,
_Out_ PHKEY phkResult phkResult,
_Out_opt_ LPDWORD lpdwDisposition = 0
);
其中hKey
的意思是A handle to an open registry key
,也就是一个一个开放注册表项的句柄
而lpSubKey
的意思此函数打开或创建的子项的名称
这里我们大概知道这个函数会创建一个叫
SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
的键,这个键按照字面意思winlogon
理解就是登录,这个在MSDN
中的解释是
Windows操作系统的一部分,提供交互式登录支持。
Winlogon是围绕交互式登录模型设计的,该模型由三部分组成:Winlogon可执行文件,图形标识和认证动态链接库(DLL)(称为GINA)以及任意数量的网络提供程序。
下一个调用的函数是RegSetValueExA
这个函数,这个函数我们注意这个ValueName
和lpData
的值,一个是键的名字,一个是键的值
我们已经运行过了这个代码,所以我们现在可以打开注册表来查看这个位置的值
可以看见,我们这里的的GinaDLL
的值被设置成了msgina32.dll
的绝对路径,不管你在吧这个Lab11-01.exe
放哪里,都可以将对应的msgina32.dll
的绝对路径写入键值中
之后,函数就关闭句柄,然后就退出了
下面我们进行的msgina32.dll
的分析
我们打开这个msgina32.dll
文件,可以看到这样的结构
我们查看字符串
这里我们可以看到许多的函数名,还有一些DLL
的名称
在这里我们并没有看见书中说的那些字符串的位置,然后我们进入高级静态分析的过程
先找到DLL
的开始的地方DllMain
这个地方,可以看到如下的样子
这里从外部传入了一个fdwReason
参数,这个fdwReason
参数的意义是
reason code说明了为什么调用DLL入口函数
而这个是正DllMain
的结构函数
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL,
_In_ DWORD fdwReason,
_In_ LPVOID lpvReserved
);
这里将fdwReason
和1
进行了比较,之后跳转,这里我们要明确一下这个1
代表什么意义在DllMain
中
这里我们可以看到这个1
代表的意思是DLL_PROCESS_ATTACH
,而它的解释翻译过来是这样的
由于进程启动或由于调用LoadLibrary,DLL正被加载到当前进程的虚拟地址空间中。 DLL可以使用此机会初始化任何实例数据或使用TlsAlloc函数分配线程本地存储(TLS)索引。
看不懂,大概意思是这个DLL
会被调用程序加载进调用程序的虚拟地址空间之中,这也就是ATTACH
的意思,想起OD
的ATTACH
功能了不?
然后就是一个jnz
跳转,我们考虑一下这里的jnz
会在什么情况之下跳转
cmp eax, 1
之后,如果eax
也就是fdwReason
的值是1
的话,cmp
之后,ZF
=1
,之后jnz
不会跳转,而如果eax
的值不是1
的话,ZF
=0
,之后jnz
跳转,跳转之后执行的是函数清理和退出
所以这里的代码期望的值是1
,这样就可以继续执行如下的代码
这里执行的第一个函数是DisableThreadLibraryCalls
,这个函数的解释是
为指定的动态链接库(DLL)禁用DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知,这可以减少某些应用程序的工作集大小
之后下一个调用是GetSystemDirectoryW
,这个函数的解释是
检索系统目录的路径,系统目录包含系统文件,如动态链接库和驱动程序
此功能主要是为了兼容性而提供的。应用程序应将代码存储在Program Files文件夹中,并将持久数据存储在用户配置文件的Application Data文件夹中
下一个调用函数lstrcatW
,这个函数熟悉的C
语言的同学应该非常熟悉了
追加一个字符串到另一个
这个函数会将两个字符串拼接在一起
LPTSTR WINAPI lstrcat(
_Inout_ LPTSTR lpString1 = ecx,
_In_ LPTSTR lpString2 = "\\MSGina"
);
这里我们可以看到,第一个字符串的值是ecx
,而要拼接在ecx
之后的字符串是\\MSGina
,而我们也可以知道,这个ecx
的值是GetSystemDirectoryW
的返回值
在MSDN
中,这样解释lpBuffer
指向接收路径的缓冲区的指针,除非系统目录是根目录,否则此路径不会以反斜杠结尾
例如,如果系统目录在驱动器C上名为Windows\System32,则此函数检索到的系统目录的路径为C:\Windows\System32
所以我们可以预测这个ecx
的值是C:\Windows\System32
,之后,和字符串\\MSGina
拼接之后,就成了
C:\Windows\System32\MSGina
下一个要调用的函数是LoadLibraryW
,这个函数在MSDN
中的解释是
将指定的模块加载到调用进程的地址空间中,指定的模块可能会导致其他模块被加载
这里我们可以分析得出,这个[esp+20Ch+Buffer]
其实就是
C:\Windows\System32\MSGina
所以这里的的LoadLibraryW
其实要加载的DLL
就是上面那个路径上的MSGina
,这里需要我们注意一下,这个路径用LoadLibraryW
调用之后,会加载的DLL
其实是C:\Windows\System32\msgina.dll
这里稍微解释一下这个原因
在MSDN
中对于LoadLibraryW
的lpFileName
的原文解释就是
The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). The name specified is the file name of the module and is not related to the name stored in the library module itself, as specified by the LIBRARY keyword in the module-definition (.def) file.
If the string specifies a full path, the function searches only that path for the module.
If the string specifies a relative path or a module name without a path, the function uses a standard search strategy to find the module; for more information, see the Remarks.
If the function cannot find the module, the function fails. When specifying a path, be sure to use backslashes (), not forward slashes (/). For more information about paths, see Naming a File or Directory.
If the string specifies a module name without a path and the file name extension is omitted, the function appends the default library extension .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point character (.) in the module name string.
这里对我们理解问题有用的是最后一段
If the string specifies a module name without a path and the file name extension is omitted, the function appends the default library extension .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point character (.) in the module name string.’
这段翻译过来就是
如果字符串指定一个没有路径的模块名称,并且省略了文件扩展名,则该函数会将缺省库扩展名.dll附加到模块名称中。为防止函数将.dll附加到模块名称中,请在模块名称字符串中包含尾部字符(.)
在这里LoadLibraryW
函数会认为字符串C:\Windows\System32\MSGina
是个省略了文件后缀名的字符串,程序会自动补全,也就是会去加载
C:\Windows\System32\MSGina.dll
上的DLL
,这个地址上的也就是C:\Windows\System32\msgina.dll
所以这里为什么没有后缀名也会去加载msgina.dll
,原因就是这样
之后函数就是做了清理工作就退出了
书上说
msgina.dll是实现GINA的Windows DLL,然而msgina32.dll是拦截GINA的恶意DLL程序。设计成msgina.dll名字的目的显然是为了欺骗分析人员
之后这个代码会将打开的msgina.dll
的句柄保存在hLibModule
中,这个变量是个全局变量,使用这个变量msgina32.dll
会在合适的时候调用msgina.dll
中的函数,但是这个函数明显是被msgina32.dll
劫持了,这样可以保持系统的正常运行
到这里为止,我们已经完成了DllMain
的分析,之后我们分析这个dll
的导出函数
这些导出函数我们可以在这里找到
这里我们可以随便点一个函数,比如第一个函数
之后我们可以看到这个hLibModule
,佐证了这个变量是全局变量
我们还是按照书上的步骤来走,先来分析一下这个WlxLoggedOnSAS
函数的代码
这个函数打开之后就可以看到这些代码
这个函数压栈了一个参数WlxLoggedOnSAS
,然后调用了sub_10001000
,很巧的是,这个函数就是我们刚刚随便点的那个函数
这里函数会用全局变量hLibModule
这个打开的msgina.dll
的句柄来赋值给eax
然后下一个函数调用是GetProcAddress
,这个函数的定义就是
从指定的动态链接库(DLL)中检索导出的函数或变量的地址
而这个要检索的DLL
就是msgina.dll
,这个是由eax
来决定的,而eax
刚刚才被赋值为hLibModule
而这个lpProcName
其实就是刚刚上面压栈的那个字符串WlxLoggedOnSAS
,下面我们来分析一下这个
执行完如下代码
push offset aWlxLoggedons_0
之后,栈空间如下
----------------
| WlxLoggedOnSAS | <--- esp
----------------
| ... |
----------------
.
.
.
----------------
| ... | <--- ebp
----------------
进入调用函数之后,会第一时间push
一个返回地址入栈,这个操作在代码上是不会显示的,但是却是实实在在操作了的
----------------
| retuen address | <--- esp
----------------
| WlxLoggedOnSAS |
----------------
| ... |
----------------
.
.
.
----------------
| ... | <--- ebp
----------------
sub esp, 10h
----------------
| ... |-- <--- esp
---------------- |
| ... | |
---------------- |---> 0x10h = 16d
| ... | |
---------------- |
| ... |--
----------------
| retuen address |
----------------
| WlxLoggedOnSAS |
----------------
| ... |
----------------
.
.
.
----------------
| ... | <--- ebp
----------------
之后就是下面这句操作
push esi
----------------
| esi | <--- esp
----------------
| ... |--
---------------- |
| ... | |
---------------- |---> 0x10h = 16d
| ... | |
---------------- |
| ... |--
----------------
| retuen address |
----------------
| WlxLoggedOnSAS |
----------------
| ... |
----------------
.
.
.
----------------
| ... | <--- ebp
----------------
之后的指令操作,注意这个指令比较关键
mov esi, [esp+14h+lpProcName]
而lpProcName
在函数的开头就标注了值是4
,但是注意一下这里标注的数据类型是dword ptr
类型
这里有两个类型,一个是byte ptr -10h
,就是上面那个var_10
,一个是dword ptr
的类型,这两个类型是完全不同的两个数据类型
其中byte
是一个字节的数据,而dword
是两个字节的数据
我们推导一下esp+18h
是哪里
从上图简略的栈图中,我们可以看出,存储esi
的空间占了四个地址,加上刚刚申请的0x10h
个字节,就是0x14h
个字节的空间
于是我们得到esp+18h
的值
----------------
| esi | <--- esp
----------------
| ... | <--- esp+4h
----------------
| ... | <--- esp+8h
----------------
| ... | <--- esp+ch
----------------
| ... | <--- esp+10h
----------------
| retuen address | <--- esp+14h
----------------
| WlxLoggedOnSAS | <--- esp+18h
----------------
| ... |
----------------
.
.
.
----------------
| ... | <--- ebp
----------------
mov esi, [esp+18h]
于是我们可以看出,这里的esi
最后会指向WlxLoggedOnSAS
字符串的上一个数据
(PS:这里我认为是作者写恶意代码的时候的一个错误或者什么的,反正我现在只能分析得出这些结论,这些问题我已经反馈给他们的出版社的老大了,不知道会不会回我 2018-4-16)
(2018-4-23)查明是因为函数调用后第一时间会push
一个返回地址进去,但是这个操作不会体现在汇编代码中
这样,而不是想移动,再赋值,所以esp
指向的地方永远是空值
所以这里的函数会在用GetProcAddress
来在真正的msgina.dll
中找到函数WlxLoggedOnSAS
的地址,之后
之后如果函数调用成功,会做一个判断跳转,这里依旧是假设我们的函数调用成功
就会执行下面这些函数
这里的第一个指令是
mov ecx, esi
之后做的操作是
shr ecx, 10h
这句指令的意思是将ecx
指向的那个字符串的指针的值置为0
,esi
的值并未改变
因为shr
是逻辑右,左边用0
补齐,所以移动0x10h
之后,整个指针的16bit
都为了0
因为最后的结果为0
,所以ZF
=1
,则JNZ
不会跳转,最后会按照红线的路线执行
这里会打印一个字符串,这里的var_10
=-10
,最后其实寻找的地址是
[esp+8h]
这里我们就不去推理这个地址上是什么了
然后我们注意这里的eax
,自从调用了GetProcAddress
之后,就一直未改变过
这里可以看到,最后一次出现eax
是
test eax, eax
这句代码,之后就再也没改变过eax
的值,这里的eax
的值代表了WlxLoggedOnSAS
在msgina.dll
中的地址,真实的那个WlxLoggedOnSAS
的地址,之后
函数调用完sub_10001000
之后就会跳转到eax
的位置,这个位置就是WlxLoggedOnSAS
的真实地址,也就是这里只是劫持了WlxLoggedOnSAS
函数,然后执行完恶意程序作者的自己的代码sub_10001000
之后就又会跳转到真实的WlxLoggedOnSAS
函数去执行
这样保证了系统的正常运行
下面书上的说法是
如果我们继续分析其他导出函数,将看到大部分于WlxLoggedOnSAS(它们是中转函数)中的类似操作,然而WlxLoggedOutSAS例外,它包含了一些额外的代码(当系统注销时调用WlxLoggedOutSAS)
下面我就缩短分析的步骤,直接去分析这个WlxLoggedOutSAS
函数
上图是这个函数第一部分
我们可以看到这个函数的第一个调用是sub_10001000
,但是这次传入的参数是WlxLoggedOutSAS
这个字符串
我们可以知道,这个函数传入一个字符串之后,就会调用GetProcAddress
来查找这个函数在真正的msgina.dll
中的地址
这里有个IDA
中无法注释的函数
我们打开看看会发现
这个调用的函数是在MSVCPRT
中的函数,用IDA
可能还不好看,可以用OD
打开看看
可以看到,这个有点像乱码的函数和fclose
和fwprintf
都是出自MSVCRT
的
下一个调用是
这里的edi
我们可以看看是从哪里来的参数
这里的edi
我们可以从代码就看出来,就是eax
,而eax
是调用sub_10001000
之后的的返回值,也就是函数WlxLoggedOutSAS
的真实地址
所以这个代码会在这里去调用真实的WlxLoggedOutSAS
函数,但是这个函数的入参我们需要注意一下,分析图中的各种mov
和push
操作我们可以得到下面的参数表
int WlxLoggedOutSAS(
_In_ PVOID pWlxContext = [esp+1Ch+arg_0] = [esp+20h],
_In_ DWORD dwSasType = [esp+18h+arg_4] = [esp+20h],
_Out_ PLUID pAuthenticationId = [esp+14h+arg_8] = [esp+20h],
_Inout_ PSID pLogonSid = [esp+0Ch+arg_C] = [esp+1ch],
_Out_ PDWORD pdwOptions = [esp+0Ch+arg_10] = [esp+20h],
_Out_ PHANDLE phToken = [esp+0Ch+arg_14] = [esp+24h],
_Out_ PWLX_MPR_NOTIFY_INFO pNprNotifyInfo = [esp+0Ch+arg_18] = [esp+28h],
_Out_ PVOID *pProfile = [esp+0Ch+arg_1C] = [esp+2ch]
);
这里就直接会调用在真正msgina.dll
中的函数
调用之后就会执行下面的函数
这里调用了一个函数sub_10001570
,这个函数我们可以看看是什么
这里入栈的参数有一个eax
和aUnSDmSpwSOlds
这个东西
函数的开始,第一个call
是调用了_vsnwprintf
这个函数,这个函数使用是这样的
int _vsnprintf(char *buffer, size_t max_count, const char *format, va_list vArgList);
这里的各个参数之间是作用是这样的
1. char *buffer [out],把生成的格式化的字符串存放在这里.
2. size_t max_count [in], buffer可接受的最大字节数,防止产生数组越界.
3. const char *format [in], 格式化字符串
4. va_list vArgList [in], va_list变量. va:variable-argument:可变参数
对照我们的汇编代码,我们可以得出下面结论
buffer = edx = [esp+54h]
max_count = 800h
format = "UN %s DM %s PW %s OLD %s"
vArgList = eax [esp+860h]
这里需要注意的是[esp+54h]
和[esp+860h]
均是指向sub_10001570
函数调用之前栈空间,这里我们就没法溯源这些数据到底是什么了
之后函数会调用_wfopen
,这个函数会打开一个指向文件的文件句柄
FILE *_wfopen(
const wchar_t *filename,
const wchar_t *mode
);
这里的两个入参是这样的
filename = msutil32.sys
mode = 61h
我们查一下这个sys
是什么,查不到,查出来的都是这个书的解题答案的网站
书上说或
_vsnwprintf 调用填充了传入
WlxLoggedOutSAS
导出函数的格式化字符串
这个字符串的格式是这样的
UN %s DM %s PW %s OLD %s
这里我们可以猜测这个UN
可能是UserName
的简写
而PW
可能是PassWord
的简写
根据这个对应关系,我们回到调用这个函数之前的地方,可以得出一下结论
这里的eax
对应的应该就是用户的UserName
,而[esi+4]
则对应了DM
,[esi+8]
对应了PassWord
,最后的[esi+0ch]
对应了我们的OLD
我们溯源回去可以发现,eax
其实就是调用真正的WlxLoggedOutSAS
的一个参数,在[esp+0Ch+arg_18]
上
我们画出函数调用sub_10001570
时候的栈图
--------------
| 0 | <--- esp
--------------
|aUnSDmSPwSOlds|
--------------
| UN |
--------------
| DM |
--------------
| PW |
--------------
| OLD |
--------------
| --- |
--------------
之后我们进入了函数之后,第一时间会压栈返回地址
--------------
|return address| <--- esp
--------------
| 0 |
--------------
|aUnSDmSPwSOlds|
--------------
| UN |
--------------
| DM |
--------------
| PW |
--------------
| OLD |
--------------
| --- |
--------------
所以这里我们可以看出,赋值给ecx
的[esp+8]
其实就是那个格式化字符串的地址aUnSDmSPwSOLDs
然后,会有一个sub
操作来扩大栈
--------------
| | --- <--- esp
-------------- |
. |
. | ---> 0x854h
. |
-------------- |
|return address| ---
--------------
| 0 |
--------------
|aUnSDmSPwSOlds|
--------------
| UN |
--------------
| DM |
--------------
| PW |
--------------
| OLD |
--------------
| --- |
--------------
之后的eax
其实就是UN
的地址
但是我们这里注意这个格式化字符串,需要4
个入参,于是函数会自动往后依次去四个数据,也就是把那四个缩写都取了进去
由于我们这里不知道具体变量UN
代表了什么值,所以这里我们用小写来代替它的值
于是我们可以这样写这个调用函数
_vsnwprintf(esp+54, 800h, "UN %s DM %s PW %s OLD %s", un, dm, pw, old)
我们可以看出,这里是将变量都格式化到了字符中,然后保存在esp+54
的地址上
我们理清这个栈关系之后,我们就可以继续下面的分析
这个函数_wstrtime
的作用是
将时间复制到缓冲区
这里的Dest
其实就是我们刚刚格式化字符串的那个缓冲区的地址,但是这里没用到
_wstrtime
的入参只需要一个,这里我们记住Buffer
这里存储的是时间字符串
下一个调用函数是_wstrdate
其实就是获得日期的函数
之后
之后函数就会调用fwprintf
函数来格式化一个字符串
这里是定义
int fwprintf(
FILE *stream,
const wchar_t *format [,
argument ]...
);
这里的esi
我们回头去看其实就是打开msutil32.sys
的那个句柄
这里的eax
则是上面的上面那个格式化字符串操作之后保存结果的地址
我们这里再截一个图来看整体的代码
这里分析的时候要注意的是这里的三个参数不是一次性push
入栈的,是分了三次
最后一个push
的eax
代表了_wstrdate
的返回值
而倒数第二个的eax
代表了_wstrtime
的返回值
倒数第三个的eax
代表了我们前面格式化字符串后的那个字符串
我们可以写出伪代码的样子就是这样
fwprintf(esi, "%s %s - %s ", date, time, "UN un DM dm PW pw OLD old")
于是这个程序会把这些信息写入了msutil32.sys
中
如果我们重启机器,然后就可以在msutil32.sys
中发现我们的用户和密码
我们现在验证一下~
我们在
C:\WINDOWS\system32\
中可以找到这个文件
然后我们打开看看
我们可以发现,我们的信息已经被记录下来了,我这里用的是空密码,所以PW
后面是个空值
病毒分析到这里基本我们就清除他的运作方式了
于是我们可以来回答这个问题也顺便回顾一下这个病毒
解答:这个恶意代码会从名叫TGAD
的资源节提取一个文件并命名为msgina32.dll
2. 这个恶意代码如何进行驻留?
解答:这个恶意代码会在注册表中添加一个键值来安装这个DLL
系统重启之后,依旧会加载这个DLL
3. 这个恶意代码如何窃取用户登录凭证
解答:恶意代码用GINA
机制来拦截用户的登录凭证,msgina32.dll
会拦截所有提交到系统认证的用户登录凭证
4.这个恶意代码对窃取的凭证做了什么处理?
解答:这个恶意代码会将凭证保存在C:\WINDOWS\system32\msutil32.dll
中
5.如何在你的测试环境让这个恶意代码获得用户登录凭证?
解答:重启系统
本文完