Malware Analysis Qakbot分析

0.分析目标

  • 恶意程序是否加壳?如果是,是常见的壳还是自写壳?
  • 恶意程序使用什么网络通信手段?Winsock2、Wininet、COM、WSK甚至是自己实现的?
  • 是否存在注入或者HOOK技术?如果存在,是什么?
  • 是否存在对抗?反调试、反静态分析、反虚拟机?
  • 是否存在API/DLL被加密?
  • 是否存在字符串加密或混淆?
  • 恶意程序使用了什么同步原语?
  • 恶意程序使用了什么加密算法?
  • 恶意程序使用了什么持久化手段?
  • 是否存在ShellCode被注入进系统进程?
  • 是否存在文件过滤驱动被安装?
  • 如果安装了驱动,是否存在回调或者定时器?

1.信息收集

BAZAAR 报告:

BAZARR 数据库报告

TRIAGE报告:

TRIAGE报告1

TRIAGE报告2

沙箱报告:

沙箱报告

DIE检测:

DIE检测

PE信息:

导入表信息

导入表信息1

导入表信息2

导入表信息3

节表信息

节表信息

导出表信息

导出表信息

总结:

  • 样本来自qakbot家族,属于obama150僵尸网络。
  • 有遍历进程以及读写其他进程的API被调用,可能存在注入。
  • 可能是使用计划任务作为持久化。
  • 可能将自己释放到temp目录下。
  • 注入的目标进程可能是explorer.exe。
  • 程序是由Borland Delphi编写的32位DLL。
  • 导入表中未出现网络相关的API,可能被动态解析或者是加壳了。
  • 存在不寻常的节区名。
  • 存在一个导出函数,可以被用于动态调试。

2.脱壳

调试参数:

1
"C:\Windows\SysWOW64\rundll32.exe" C:\Users\mas\Desktop\mal_lab\test2\mas_2.bin,#1

eip指向oep时,在如下API设置断点:

  • VirtualAlloc( ) ret 0x10 // 检测自注入
  • WriteProcessMemory( ) // 根据上述信息,可能存在进程注入
  • NtResumeThread( ) // 为了避免失去控制权

设置好断点后,F9运行,每次断在VirtualAlloc返回位置时,将eax的值放入内存窗口中,按下F9观察内存中的内容。遇见异常按Shift + F9忽略继续执行。直到ResumeThread被断下时,此时会得到两个PE文件,如下:

PE1

PE2

第一个PE文件的节名异常,可能还没有脱完,将第二个PE所在的内存区域保存到文件rundll32_02900000.bin。

Trige报告中显示,可能存在注入,动态跟一下,在以下API设置断点:(这里没设置MapViewOfSection断点的原因是经过测试,断不下,API被重写了)

  • CreateProcessW( )
  • ResumeThread( )

重新运行,断点断在CreateProcessW(),创建msra.exe系统进程。

CreateProcessW()

执行到这里直接在ZwMapViewOfSection( )下断断不到想要的结果,这里采用动态跟踪的方式,向下追踪代码,可以得到如下调用ZwMapViewOfSection的方式:

调用ZwMapViewOfSectiond

这里有个对抗技巧,根据eax + 0x14得到的地址根本不在ntdll中,而是在它释放的一个随即名dll中,相当于ntdll中的api被重写了一遍。

可以看见重写api列表如下:

重写的api列表

函数地址来自a0f1f0bb.dll模块的.text段,这个模块就是上面得到那个第一个pe文件。

a0f1f0bb.dll

步过这个函数,可以看见msra.exe被挂起创建,并且多了一个132kb的可执行共享内存,如下:

msra.exe

映射内存可以直接由memcpy进行读写,这里不去追踪了,直接F9放行,断到ResumeThread( )函数位置:

ResumeThread

此时使用ProcessHacker将即将执行的镜像内存dump下来,可以得知与之前dump文件是同一个pe的两种状态,如图所示:

010对比

手动修复PE:

  • 将所有节表项的ra用va替换
  • 计算相邻节表项之间的offset,用作rs和vs
  • 将ImageBase修改为内存dump的起始位置

手动修复后

工具修复PE:

使用pe_unmapper修复一下内存中的dump,将其转换成未映射状态:

libpeconv/pe_unmapper at master · hasherezade/libpeconv (github.com)

1
2
// pe_unmapper.exe /in 输入文件 /base 映射基址 /out 输出文件
pe_unmapper.exe /in msra.exe_0x450000-0x21000.bin /base 00450000 /out msra.exe_0x450000-0x21000_fixed.bin

转换后的pe文件导入表可以正常解析:

查看导入表

3.逆向

3.0 IDA技巧

  • Ctrl + F5 反编译整个文件
  • Shift + F11 检查 mssdk_win7、ntapi_win7、ntddk_win7 是否存在,如果不存在,按INS添加。将vc32rtf添加到Shift + F5,分析驱动使用win10的库
  • 运行 Flare Capa Explorer 和 FindCrypt 插件来收集信息
  • 遇见申请大块堆内存,并且后续代码将该内存当数组处理的情况,可以创建大小一致结构体,将该堆内存空间指定为结构体类型,可以美化伪代码
  • 多尝试使用枚举类型恢复API中的常量flag

3.1 自动化处理加密的字符串

3.1.1 Python脚本模拟算法

DllEntry向下不远处,会看到两个算法函数,如图所示:

DllEntry入口

decode_string_table函数实现如下:

decode_string_table

参数列表:

1
j_decode_string_table(加密字符串表长度, 全局加密字符串表,全局解密key,未使用,字符串表索引)

j_decode_string_table函数实现如下:

j_decode_string_table

该函数会根据传入的索引来解密相应的字符串,并申请堆内存来保存结果,返回堆内存指针。

经过测试,decode_string_table_2函数与decode_string_table功能一致,只不过block和key的地址变换了。

编写Python脚本模拟算法:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
from pydoc import doc
import pefile

def decrypter(id, data_string, data_key):
size = 3660
index = id
index2 = 0
if( id < size ):
flag = False
while True:
if(data_key[index % 0x5A] == data_string[index]):
break
index += 1
if(index >= size):
flag = True
break
if(flag == False):
index2 = index - id

decopded_buf = ''
index = 0
if(index2 != 0):
while True:
decopded_buf += chr((data_string[id]) ^ (data_key[id % 0x5A]))
id += 1
index += 1
if(index >= index2):
break
return decopded_buf

def extract_data(filepath):
pe = pefile.PE(filepath)
for section in pe.sections:
if '.data' in section.Name.decode(encoding = 'utf-8').rstrip('x00'):
return (section.get_data(section.VirtualAddress, section.SizeOfRawData))


def calc_offsets(s_seg_start, x_start):
data_offset = hex(int(x_start, 16) - int(s_seg_start, 16))
return data_offset


def string_decrypter(id, data_seg_start, encrypted_string_addr, key_data_addr):
# 两个缓冲区
data_1 = b''
data_2 = b''

# 计算偏移
encrypted_string_addr_rel = calc_offsets(data_seg_start, encrypted_string_addr)
key_data_addr_rel = calc_offsets(data_seg_start, key_data_addr)

# 获取.data数据
filepath = r"C:\\Users\\yuanmingfei\\Desktop\\mas_2\\mas_2_dump.bin "
data_encoded_extracted_1 = extract_data(filepath)
data_encoded_extracted_2 = extract_data(filepath)

# 计算data和key的终止偏移
d1_off = 0x0
d2_off = 0x0
if (b'\x00\x00' in data_encoded_extracted_1[int(encrypted_string_addr_rel, 16): ]):
d1_off = (data_encoded_extracted_1[int(encrypted_string_addr_rel, 16): ]).index(b'\x00\x00')
if (b'\x00\x00' in data_encoded_extracted_2[int(key_data_addr_rel, 16): ]):
d2_off = (data_encoded_extracted_2[int(key_data_addr_rel, 16): ]).index(b'\x00\x00')

# 提取data和key
data_1 = data_encoded_extracted_1[int(encrypted_string_addr_rel, 16): int(encrypted_string_addr_rel, 16) + d1_off]
data_2 = data_encoded_extracted_2[int(key_data_addr_rel, 16): int(key_data_addr_rel, 16) + d2_off]

# 解密数据
print(decrypter(id, data_1, data_2))

def main():
string_decrypter(708, '0x1001D000', '0x1001D0B0', '0x1001D050')

if __name__ == '__main__':
main()

3.1.2 编写IDA脚本实现自动注释

交叉引用后发现,大量位置使用该函数来解密字符串,编写IDA脚本实现自动对引用位置添加注释:

经过查看参数传递,发现大多数情况index均使用ecx以及push指令传参,并在距离call指令1-2个指令处,脚本遍历解密函数的全部交叉引用,并提取指令中的立即数作为index来解密字符串,再对当前位置添加注释。代码如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from pydoc import doc
import pefile
import idaapi
import idautils
import binascii

def decrypter(id, data_string, data_key):
size = 3660
index = id
index2 = 0
if( id < size ):
flag = False
while True:
if(data_key[index % 0x5A] == data_string[index]):
break
index += 1
if(index >= size):
flag = True
break
if(flag == False):
index2 = index - id

decopded_buf = ''
index = 0
if(index2 != 0):
while True:
decopded_buf += chr((data_string[id]) ^ (data_key[id % 0x5A]))
id += 1
index += 1
if(index >= index2):
break
return decopded_buf

def extract_data(filepath):
pe = pefile.PE(filepath)
for section in pe.sections:
if '.data' in section.Name.decode(encoding = 'utf-8').rstrip('x00'):
return (section.get_data(section.VirtualAddress, section.SizeOfRawData))


def calc_offsets(s_seg_start, x_start):
data_offset = hex(int(x_start, 16) - int(s_seg_start, 16))
return data_offset


def string_decrypter(id, encrypted_string_addr, key_data_addr):
# 两个缓冲区
data_1 = b''
data_2 = b''

encrypted_string_addr_ref = hex(int(encrypted_string_addr))
key_data_addr_ref = hex(int(key_data_addr))

# 获取.data地址
for segment in idautils.Segments():
if '.data' == idc.get_segm_name(segment):
data_seg_start = hex(int(idc.get_segm_start(segment)))

# 计算偏移
encrypted_string_addr_rel = calc_offsets(data_seg_start, encrypted_string_addr_ref)
key_data_addr_rel = calc_offsets(data_seg_start, key_data_addr_ref)

# 获取.data数据
filepath = r"C:\\Users\\yuanmingfei\\Desktop\\mas_2\\mas_2_dump.bin"
data_encoded_extracted_1 = extract_data(filepath)
data_encoded_extracted_2 = extract_data(filepath)

# 计算data和key的终止偏移
d1_off = 0x0
d2_off = 0x0
if (b'\x00\x00' in data_encoded_extracted_1[int(encrypted_string_addr_rel, 16): ]):
d1_off = (data_encoded_extracted_1[int(encrypted_string_addr_rel, 16): ]).index(b'\x00\x00')
if (b'\x00\x00' in data_encoded_extracted_2[int(key_data_addr_rel, 16): ]):
d2_off = (data_encoded_extracted_2[int(key_data_addr_rel, 16): ]).index(b'\x00\x00')

# 提取data和key
data_1 = data_encoded_extracted_1[int(encrypted_string_addr_rel, 16): int(encrypted_string_addr_rel, 16) + d1_off]
data_2 = data_encoded_extracted_2[int(key_data_addr_rel, 16): int(key_data_addr_rel, 16) + d2_off]

# 解密数据
decryped = decrypter(id, data_1, data_2)

# 拼接注释
comment = ("string[%d]: %s" % (id, decryped))

return comment

def make_decompiler_comments(addr, comment):
c_function = idaapi.decompile(addr)
treeloc_struct = idaapi.treeloc_t()
treeloc_struct.ea = addr
treeloc_struct.itp = idaapi.ITP_SEMI
if c_function:
c_function.set_user_cmt(treeloc_struct, comment)
c_function.save_user_cmts()

def comment_string_offset(encrypted_string_addr, key_data_addr, fun_offset):
# 获取线性地址
str_function = idc.get_name_ea_simple(fun_offset)
# 遍历函数交叉引用
for k in idautils.CodeRefsTo(str_function, 0):
# 获取前一条指令地址
p = idc.prev_head(k)
# 获取地址处的反汇编
my = idc.print_insn_mnem(p)
if my in ('mov', 'push'):
if my == 'mov':
# 判断汇编操作数类型
if idc.get_operand_type(p, 1) == 5:
# 获取操作数
str_off = int(idc.print_operand(p, 1)[:-1], 16)
comment_string = string_decrypter(str_off, encrypted_string_addr, key_data_addr)
# 添加注释
idc.set_cmt(k, comment_string, 0)
make_decompiler_comments(k, comment_string)
if my == 'push':
if idc.get_operand_type(p, 0) == 5:
str_off = int(idc.print_operand(p, 0)[:-1], 16)
comment_string = string_decrypter(str_off, encrypted_string_addr, key_data_addr)
idc.set_cmt(k, comment_string, 0)
make_decompiler_comments(k, comment_string)
else:
j = idc.prev_head(p)
my2 = idc.print_insn_mnem(j)
if my2 in ('mov', 'push'):
if my2 == 'mov':
if idc.get_operand_type(j, 1) == 5:
str_off = int(idc.print_operand(j, 1)[:-1], 16)
comment_string = string_decrypter(str_off, encrypted_string_addr, key_data_addr)
idc.set_cmt(k, comment_string, 0)
make_decompiler_comments(k, comment_string)
if my2 == 'push':
if idc.get_operand_type(j, 0) == 5:
str_off = int(idc.print_operand(j, 0)[:-1], 16)
comment_string = string_decrypter(str_off, encrypted_string_addr, key_data_addr)
idc.set_cmt(k, comment_string, 0)
make_decompiler_comments(k, comment_string)


comment_string_offset(0x1001D5A8, 0x1001E3F8, "decode_string_table_1_1")
comment_string_offset(0x1001D5A8, 0x1001E3F8, "decode_string_table_1_2")
comment_string_offset(0x1001D0B0, 0x1001D050, "decode_string_table_2_1")
comment_string_offset(0x1001D0B0, 0x1001D050, "decode_string_table_2_2")
print("Done!")

执行结果如下:

自动注释脚本

只有少部分使用寄存器间接传参的,其他的情况均可正常解析。

使用到的ida_api如下:

ida_idaapi API documentation (hex-rays.com)

idautils API documentation (hex-rays.com)

idc API documentation (hex-rays.com)

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
36
37
38
39
40
41
# 头文件
import idaapi
import idautils

# 遍历节表
for segment in idautils.Segments():
# 过滤节名字
if '.data' == idc.get_segm_name(segment):
# 获取节首地址
data_seg_start = hex(int(idc.get_segm_start(segment)))

# 根据函数名获取函数地址
idc.get_name_ea_simple(func_name)

# 得到某个地址的交叉引用地址列表
idautils.CodeRefsTo(addr, 0)

# 获取指定地址指令的上一条指令地址
idc.prev_head(addr)

# 获取地址处指令Opcode
idc.print_insn_mnem(addr)

# 获取地址处指令操作数(0: 操作数1;1:操作数2)
idc.get_operand_type(addr, 1)

# 打印地址处的指令操作数(0: 操作数1;1:操作数2)
idc.print_operand(addr, 1)

# 指定地址添加注释
idc.set_cmt(addr, comment, 0)

# 伪代码指定地址的函数中添加注释
def make_decompiler_comments(addr, comment):
c_function = idaapi.decompile(addr)
treeloc_struct = idaapi.treeloc_t()
treeloc_struct.ea = addr
treeloc_struct.itp = idaapi.ITP_SEMI
if c_function:
c_function.set_user_cmt(treeloc_struct, comment)
c_function.save_user_cmts()

3.2 处理动态解析API

3.2.1 整体逻辑分析

字符串解密函数不远处的sub_1000E369函数就是用来解析API的,代码如下:

解析API

pe文件可能包括导入dll,这个函数根据字符串解密结果来解析指定dll,并在堆中构建新的IAT表,将地址返回。

整个解析过程如下图所示:

API动态解析逻辑图

3.2.2 Hash函数名还原

经过分析,样本中有一块全局数据区,存放的是所有经过hash化的导入函数名,每个hash串长度为4字节,hash算法为crc32,代码如下:

crc32

hash使用的key为0x218FE95B,代码如下:

hash_key其中的0x218FE95B为hash算法用到的xor key,使用Hash DB插件来自动化解析hash数据,首先在插件中设置xor key,如下图所示:

插件设置xor_key

将参数的data转变成如下格式:

hash_data

对着4字节hash串,右键使用Hash DB来判断算法,如图所示:

Hash DB判断算法

选择算法后,再对这个hash串右键使用Hash DB的Lookup功能来解析,之后对连续的hash串使用Hash DB的Scan IAT即可,Hash DB会自动创建枚举类型,手动将名称全部恢复,如下图所示:

恢复hash名称

对着ma_decode_api函数按 ‘X’ 查看交叉引用,将其他位置的hash名称也恢复出来。

ma_decode_api交叉引用

hash函数名全部恢复

3.2.3 PE遍历导出表

首先是初始化PE相关的表指针,代码如下:

初始化PE指针

PE相关的IDA内置结构如下:

Sunshine’s Homepage - PE file format (sunshine2k.de)

  • _IMAGE_DOS_HEADER
  • _IMAGE_NT_HEADERS
  • _IMAGE_DATA_DIRECTORY
  • _IMAGE_EXPORT_DIRECTORY
  • _IMAGE_OPTIONAL_HEADER
  • _IMAGE_SECTION_HEADER
  • _IMAGE_IMPORT_DESCRIPTOR

遍历导出名字表,计算并比对hash,将得到的索引用来解析导出表三层结构获取导出函数地址,代码如下:

遍历导出名字表

根据导出函数地址判断是否为导出转发函数,代码如下:

判断导出转发函数

PE导出转发函数:

在一些系统dll中,如Kenel32.dll中的AddVectoredExceptionHandler()函数,实际上这个函数的真正代码为ntdll中的RtlAddVectoredExceptionHandler(),如下图所示:

AddVectoredExceptionHandler转发

此时Function RVA指向pe导出表的用于保存函数名字的内存区,并且数据格式如下:

1
NTDLL.RtlAddVectoredExceptionHandler

此时想要获取这个地址,就需要加载ntdll.dll并调用GetProcAddress来获取。

解析转发dll,加载,获取导出函数地址,代码如下:

处理转发导出函数

3.2.4 生成IAT表

函数将获取到的所有导入函数地址,用一块堆内存来保存(重构IAT表),并将堆首地址返回给上一级,函数如下:

返回IAT表

增强伪代码可读性,为之前得到的全局hash序列创建结构体,如图所示:

创建api结构

并将接收指针的全局变量全部重命名,并将类型修改为指定的结构体指针,如图所示:

全局API表命名

此时去到全局变量引用位置,代码可读性大大提升,对比如下:

修复前:

解析API代码修复前

修复后:

解析API代码修复后

3.3 加密算法识别

3.3.1 Mersenne Twister

从一个随机字符串生成函数中识别一下MT伪随机数生成算法,函数(sub_1000B82D)代码如下:

生成随机字符串函数

MT算法由几个部分组成:根据seed初始化状态函数、状态传递函数、根据状态生成随机数函数。

初始化函数如下:

MT初始化

状态传递与生成随机数函数合在一起了,代码如下:

生成随机数

3.3.2 RC4

在追网络通信ip地址来源的时候,发现了rc4相关的函数,其中初始化s_box函数代码如下:

rc4_init

rc4是异或加密,加密解密函数为同一个,代码如下:

rc4_encode

3.3.3 SHA1

在追踪网络通信的路上,可以发现存在SHA1算法函数,可以根据初始化的hash值来识别算法,循环分组处理代码:

SHA1-循环分组

80轮分组运算如下:

SHA1-80轮分组运算

3.4 持久化分析

3.4.1 定位思路

对着解密出来的函数查看交叉引用,可以看到一些关键字符串,如下图:

持久化特征字符串

定位过去依次分析,包括两种驻留方式:修改注册表Run键值和添加计划任务。

3.4.2 注册表

方式一:使用Windows API修改注册表项

API修改注册表

方式二:启动reg.exe携带参数修改注册表

创建进程修改注册表

3.4.3 计划任务

创建schtasks.exe进程添加计划任务,代码如下:

添加计划任务

3.6 C2配置提取

根据rc4_encode()函数的交叉引用向上追,会找到加载资源相关的函数,代码如下:

加载资源

可以看见加载的资源id为5812,得到的powershell的字符串后续作为解密用。

查看这个ma_load_resource()函数的交叉引用会得到还有个位置引用它,资源id为3719。其中ma_liad_resource()函数内部如下:

ma_load_resource()

对调用加载3719号资源的函数查看交叉引用,可以得到如下代码:

格式化id

很是在格式化ip地址+端口的情况,在继续追加载资源后的ma_resource_decryptor()函数,最终会得到如下解密代码:

生成SHA1_KEY

使用”\System32\WindowsPowerShell\v1.0\powershell.exe”作为明文生成rc4_key,再使用rc4对资源数据进行解密,代码如下:

rc4解密

编写Python3解密脚本:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import binascii
import pefile
import ipaddress
from Crypto.Cipher import ARC4
from Crypto.Hash import SHA1

key = b'\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'

sha1_key = SHA1.new(data=key).digest()

def data_decryptor(key_data, data):
data_cipher = ARC4.new(key_data)
decrypted_config = data_cipher.decrypt(data)
return decrypted_config

def extract_resource(filename, res_identifaction):
extracted_data = b""
pe = pefile.PE(filename)
for resource in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if hasattr(resource, 'directory'):
for res_id in resource.directory.entries:
if hasattr(res_id, 'name'):
if (res_id.name):
if (str(res_id.name)) == str(res_identifaction):
offset = res_id.directory.entries[0].data.struct.OffsetToData
resid_size = res_id.directory.entries[0].data.struct.Size

extracted_data = pe.get_memory_mapped_image()[offset:offset+resid_size]
return extracted_data

def main():
filename = r"C:\\Users\\yuanmingfei\\Desktop\\mas_2\\mas_2_dump.bin"

resource_data = extract_resource(filename, 5812)
decrypted_data = data_decryptor(sha1_key, resource_data)[20:]

print('DECRYPTED BOTNET AND CAMPATIGN ID: ', end = '')
print("\n"+34*'-')
print(decrypted_data.decode('latin1'))

resource_data = extract_resource(filename, 3719)
decrypted_data = data_decryptor(sha1_key, resource_data)

resource_item = decrypted_data[21:]

print("C2 IP ADDRESS LIST:")
print(30*'-')

k = 0
i = 0
while(k < len(resource_item)):
ip_item = resource_item[k:k+4]
ip_port = resource_item[k+4:k+6]
print("IP[%d]: %s" % (i, ipaddress.IPv4Address(ip_item)), end='')
print(int(binascii.hexlify(ip_port), 16))
k = k + 7
i = i + 1


if __name__ == '__main__':
main()

解密结果如下:

C2配置信息

共计150条C2信息

3.7 WMI分析

分析sub_1000D6D0()函数,代码如下:

sub_1000D6D0

可以得到以下信息:

1
2
rclsid: 4590F811-1D3A-11D0-1F89-00AA004B2E24
riid: DC12A687-737F-11CF-884D-00AA004B2E24

Google搜索riid,可以得知ppv类型为IWbemLocator,修改类型,按下F5刷新,伪代码如下:

WMI修复伪代码

调用ConnectServer()函数将对象绑定到 ROOT\CIMV2 命名空间,并设置身份验证信息,将得到的指针返回上一级。


Malware Analysis Qakbot分析
http://helloymf.github.io/2022/10/07/malware-analysis-qakbot-fen-xi/
作者
JNZ
许可协议