VB6拾遗:内联汇编

标签: , , , , ,

虽然VB中没有原生的内联汇编支持,但是通过轻量级COM对象可以间接实现。

严格的说这不是真正的内联汇编,因为你不能在任何地方使用,只能在轻量级对象的虚表(VTable)中使用,而且与其说是内联汇编,倒不如说是内联机器码。

为什么要使用内联汇编?比较“低级”的C语言编译器一般都支持内联汇编功能,可除了一些底层的系统,又有多少程序要用到内联汇编?更何况比较“高级”的VB?

C程序用不上内联汇编,是因为C语言提供了足够强大的功能,或者说C语言本身已经比较底层了。而VB隐藏了太多底层的东西,所以有些功能必须通过汇编来实现。别的先不说,就拿移位运算来说,除了VB以外,你见过哪钟语言没有移位运算符的?虽然可以用乘除法来模拟,但是要充分考虑各种溢出并且效率低下,我们今天就用内联汇编来实现移位运算。

首先用.idl文件定义好接口,用midl.exe编译成.tlb文件并添加引用:


[
  uuid(6BEE1180-1F4A-47D9-B192-5FF90DDF8827),
  version(1.0)
]
library BitLib
{
    importlib("stdole2.tlb");

    [
      odl,
      uuid(E690B781-B712-4E59-A643-AA4AE9EE78BE),
      nonextensible
    ]
    interface IBit : IUnknown {
        long _stdcall LeftShift(
                        [in] long a, 
                        [in] long b);
        long _stdcall RightShift(
                        [in] long a, 
                        [in] long b);
    };
};

然后添加一个标准模块:


Option Explicit

'VB6 bit shift lightweight object
'By Demon
'https://demon.tw

Private Declare Function CoTaskMemAlloc Lib "ole32.dll" (ByVal cb As Long) As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" ( _
    dest As Any, ByVal numBytes As Long)

Private Type BitVTable
    VTable(4) As Long
End Type

Private Type Bit
    pVTable As Long
    cRefs As Long
End Type

Private Type LeftShift
    ASM(3) As Long
End Type

Private Type RightShift
    ASM(3) As Long
End Type

Private m_VTable As BitVTable
Private m_pVTable As Long
Private m_LeftShift As LeftShift
Private m_RightShift As RightShift


Public Function CreateBit() As IUnknown
    Dim Struct As Bit
    Dim ThisPtr As Long
    
    If m_pVTable = 0 Then
        With m_VTable
            .VTable(0) = FuncAddr(AddressOf QueryInterface)
            .VTable(1) = FuncAddr(AddressOf AddRef)
            .VTable(2) = FuncAddr(AddressOf Release)
            
            With m_LeftShift
                .ASM(0) = &H824448B
                .ASM(1) = &HC244C8B
                .ASM(2) = &HCC2E0D3
                .ASM(3) = &HCCCCCC00
                '8B4424 08       mov     eax, dword ptr [esp+8]
                '8B4C24 0C       mov     ecx, dword ptr [esp+0C]
                'D3E0            shl     eax, cl
                'C2 0C00         retn    0C
                'CC              int3
                'CC              int3
                'CC              int3
            End With
            .VTable(3) = VarPtr(m_LeftShift)
            
            With m_RightShift
                .ASM(0) = &H824448B
                .ASM(1) = &HC244C8B
                .ASM(2) = &HCC2E8D3
                .ASM(3) = &HCCCCCC00
                '8B4424 08       mov     eax, dword ptr [esp+8]
                '8B4C24 0C       mov     ecx, dword ptr [esp+0C]
                'D3E8            shr     eax, cl
                'C2 0C00         retn    0C
                'CC              int3
                'CC              int3
                'CC              int3
            End With
            .VTable(4) = VarPtr(m_RightShift)
            
            m_pVTable = VarPtr(.VTable(0))
        End With
    End If
    
    ThisPtr = CoTaskMemAlloc(LenB(Struct))
    If ThisPtr = 0 Then Err.Raise 7
    
    With Struct
        .pVTable = m_pVTable
        .cRefs = 1
    End With
    
    CopyMemory ByVal ThisPtr, Struct.pVTable, LenB(Struct)
    ZeroMemory Struct.pVTable, LenB(Struct)
    
    CopyMemory CreateBit, ThisPtr, 4
End Function

Private Function QueryInterface(This As Bit, riid As Long, pvObj As Long) As Long
    With This
        pvObj = VarPtr(.pVTable)
        .cRefs = .cRefs + 1
    End With
End Function

Private Function AddRef(This As Bit) As Long
    With This
        .cRefs = .cRefs + 1
        AddRef = .cRefs
    End With
End Function

Private Function Release(This As Bit) As Long
    With This
        .cRefs = .cRefs - 1
        Release = .cRefs
        If .cRefs = 0 Then
            DeleteThis This
        End If
    End With
End Function

Private Function FuncAddr(ByVal pfn As Long) As Long
    FuncAddr = pfn
End Function

Private Sub DeleteThis(This As Bit)
    Dim tmp As Bit
    Dim pThis As Long
    
    pThis = VarPtr(This)
    CopyMemory ByVal VarPtr(tmp), ByVal pThis, LenB(This)
    CoTaskMemFree pThis
End Sub

再添加一个标准模块:


'By Demon
'https://demon.tw
Sub Main()
    Dim IBit As IBit
    Set IBit = Bit.CreateBit
    Debug.Print Hex$(IBit.LeftShift(1, 31))
    Debug.Print Hex$(IBit.RightShift(&H80000000, 1))
End Sub

运行一下看看效果,输出80000000和40000000,还不错。

跟普通的轻量级对象一样,Bit对象VTable的前三个指针都是QueryInterface、AddRef和Release;而后两个指针却指向一个结构的地址,而该结构的内容是一串神奇的数字:


With m_LeftShift
    .ASM(0) = &H824448B
    .ASM(1) = &HC244C8B
    .ASM(2) = &HCC2E0D3
    .ASM(3) = &HCCCCCC00
    '8B4424 08       mov     eax, dword ptr [esp+8]
    '8B4C24 0C       mov     ecx, dword ptr [esp+0C]
    'D3E0            shl     eax, cl
    'C2 0C00         retn    0C
    'CC              int3
    'CC              int3
    'CC              int3
End With
.VTable(3) = VarPtr(m_LeftShift)

With m_RightShift
    .ASM(0) = &H824448B
    .ASM(1) = &HC244C8B
    .ASM(2) = &HCC2E8D3
    .ASM(3) = &HCCCCCC00
    '8B4424 08       mov     eax, dword ptr [esp+8]
    '8B4C24 0C       mov     ecx, dword ptr [esp+0C]
    'D3E8            shr     eax, cl
    'C2 0C00         retn    0C
    'CC              int3
    'CC              int3
    'CC              int3
End With
.VTable(4) = VarPtr(m_RightShift)

很显然,这些神奇的数字就是机器码,下面注释是对应的汇编代码。将汇编转成机器码的方法很多,可以用NASM、MASM、Visual C++等。

简单介绍一下用Visual C++将汇编代码转成相应机器码的方法:


int main()
{
    _asm
    {
        mov     eax, dword ptr [esp+8]
        mov     ecx, dword ptr [esp+0Ch]
        shl     eax, cl
        retn    0Ch
    }
    return 0;
}

调试上面的代码,在汇编窗口中就能看到对应的机器码,不过复制到VB中时要注意字节的顺序并用CC(int 3指令,当然也可以用nop指令)补齐:


23:       _asm
24:       {
25:           mov     eax, dword ptr [esp+8]
00401028 8B 44 24 08          mov         eax,dword ptr [esp+8]
26:           mov     ecx, dword ptr [esp+0Ch]
0040102C 8B 4C 24 0C          mov         ecx,dword ptr [esp+0Ch]
27:           shl     eax, cl
00401030 D3 E0                shl         eax,cl
28:           retn    0Ch
00401032 C2 0C 00             ret         0Ch
29:       }

为什么VB会执行我们构造出来的机器码呢?接口中定义的方法对应着对象VTable的指针,VTable(3)指向m_LeftShift结构的地址,当调用LeftShift方法时,VB编译器生成的是一条call指令,所以该地址的内容,也就是我们构造的机器码会被执行。

赞赏

微信赞赏支付宝赞赏

随机文章:

  1. VBS统计网络流量
  2. 用VBS修改Windows用户密码
  3. PHP return语句
  4. SDK编程中的窗口居中
  5. VBS生成随机密码

2 条评论 发表在“VB6拾遗:内联汇编”上

  1. 乱码说道:

    demon,你快成神了,VB都能玩的这么变态、

  2. BayMax说道:

    看完你写的,我就觉得我完全没学过

留下回复