0

我正在尝试使用 Visual Studio 2019 中的 ml64.exe 通过 64 位程序集执行一些 Office 自动化。在调用 Office COM 接口之前,我需要调用 CoInitialize。我目前正在测试初始化​​ COM 并写入控制台(我通常不编写汇编代码)如果我注释掉行

call    CoInitialize

WriteConsoleW api 调用按预期工作并向屏幕输出消息“COM 无法初始化”但是,一旦我将调用 CoInitialize 添加回来,控制台屏幕就没有任何输出,并且也会崩溃。

; *************************************************************************
; Proto types for API functions and structures
; *************************************************************************  
EXTRN   GetStdHandle:PROC
EXTRN   WriteConsoleW:PROC
EXTRN   CoCreateInstance:PROC
EXTRN   CoInitialize:PROC
EXTRN   SysFreeString:PROC
EXTRN   SysStringByteLen:PROC
EXTRN   SysAllocStringByteLen:PROC
EXTRN   OleRun:PROC
EXTRN   ExitProcess:PROC

.const

STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12

; *************************************************************************
; Object libraries
; *************************************************************************
includelib user32.lib
includelib kernel32.lib
includelib ole32.lib   
includelib oleaut32.lib

; *************************************************************************
; Our data section. 
; *************************************************************************
.data

    strErrComFailed         dw 'C','O','M',' ','F','a','i','l','e','d',' ','t','o',' ','i','n','i','t','i','a','l','i','z','e',0,0 
    strErrOutlookFailed     dw 'F','a','i','l','e','d',' ','t','o',' ','i','n','i','t','i','a','l','i','z','e',' ','O','u','t','l','o','o','k',0,0


    ;  {0006F03A-0000-0000-C000-000000000046}
    CLSID_OutlookApplication    dd 0006f03ah

                                dw 0000h
                                dw 0000h
                                dw 0C000h
                                db 00h
                                db 00h
                                db 00h
                                db 00h
                                db 00h
                                db 46h

    ; {00063001-0000-0000-C000-000000000046}
    IID_OutlookApplication      dd 00063001h
                                dw 0000h
                                dw 0000h
                                dw 0C000h
                                db 00h
                                db 00h
                                db 00h
                                db 00h
                                db 00h
                                db 46h

    ; {00000000-0000-0000-C000-000000000046}
    IID_IUnknown                dd 00000000h
                                dw 0000h    
                                dw 0000h
                                dw 0C000h
                                db 00h
                                db 00h
                                db 00h
                                db 00h
                                db 00h
                                db 46h

; *************************************************************************
; Our executable assembly code starts here in the .code section
; *************************************************************************
.code

wcslen PROC inputString:QWORD
    LOCAL stringLength:QWORD
    mov QWORD PTR inputString, rcx
    mov QWORD PTR stringLength, 0h

continue:

    mov rax, QWORD PTR inputString
    mov rcx, QWORD PTR stringLength
    movzx eax, word ptr [rax+rcx*2]   
    test eax, eax
    je finished              
    mov rax, QWORD PTR stringLength
    inc rax
    mov QWORD PTR stringLength, rax
    jmp continue    

finished:
    mov rax, QWORD PTR stringLength
    ret

wcslen ENDP

main PROC
    LOCAL hStdOutput:QWORD
    LOCAL hErrOutput:QWORD
    LOCAL hResult:DWORD

    xor     ecx,ecx
    call    CoInitialize
    mov     DWORD PTR hResult, eax

    mov     ecx, STD_OUTPUT_HANDLE
    call    GetStdHandle
    mov     QWORD PTR hStdOutput, rax

    mov     ecx, STD_ERROR_HANDLE
    call    GetStdHandle
    mov     QWORD PTR hErrOutput, rax

    lea     rcx,strErrComFailed
    call    wcslen
    mov     QWORD PTR [rsp+32], 0
    xor     r9d, r9d    ; lpNumberOfCharsWritten
    mov     r8d, eax    ; nNumberOfCharsToWrite
    lea     rdx,QWORD PTR strErrComFailed
    mov     rcx,QWORD PTR hStdOutput
    call    WriteConsoleW

    ; When the message box has been closed, exit the app with exit code eax
    mov     ecx, eax
    call    ExitProcess
    ret 


main ENDP
End

在调用 CoInitialize 之前,WinDbg 显示以下寄存器状态:

00007ff7`563e1041 e865000000      call    Win64App+0x10ab (00007ff7`563e10ab)
0:000> r
rax=00007ff7563e1037 rbx=0000000000000000 rcx=0000000000000000
rdx=00007ff7563e1037 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff7563e1041 rsp=000000a905affa58 rbp=000000a905affa70
 r8=000000a9058d6000  r9=00007ff7563e1037 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
Win64App+0x1041:
00007ff7`563e1041 e865000000      call    Win64App+0x10ab (00007ff7`563e10ab)
0:000> r ecx
ecx=0

在调用 CoInitialize 之后,有以下寄存器状态:

0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=8aa77f80a0990000
rdx=0000000000000015 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff7563e1046 rsp=000000a905affa58 rbp=000000a905affa70
 r8=0000029af97e2620  r9=0000029af97e1440 r10=0000000000000005
r11=000000a905aff9d8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Win64App+0x1046:
00007ff7`563e1046 8945ec          mov     dword ptr [rbp-14h],eax ss:000000a9`05affa5c=00000000
0:000> r eax
eax=0

调用 GetStdHandle 后:

0:000> r
rax=0000000000000074 rbx=0000000000000000 rcx=0000029af97d2840
rdx=0000000000000015 rsi=0000000000000000 rdi=0000000000000000
rip=00007ff7563e1053 rsp=000000a905affa58 rbp=000000a905affa70
 r8=0000029af97e2620  r9=0000029af97e1440 r10=0000000000000005
r11=000000a905aff9d8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206

在调用 WriteConsoleW 时,参数似乎仍然正确,但没有输出到屏幕:

KERNEL32!WriteConsoleW:
00007ffb`a97028f0 ff258a4c0500    jmp     qword ptr [KERNEL32!_imp_WriteConsoleW (00007ffb`a9757580)] ds:00007ffb`a9757580={KERNELBASE!WriteConsoleW (00007ffb`a697b750)}
0:000> du rdx
00007ff7`563e3000  "COM Failed to initialize"
0:000> r
rax=0000000000000018 rbx=0000000000000000 rcx=0000000000000074
rdx=00007ff7563e3000 rsi=0000000000000000 rdi=0000000000000000
rip=00007ffba97028f0 rsp=000000a905affa50 rbp=000000a905affa70
 r8=0000000000000018  r9=0000000000000000 r10=0000000000000005
r11=000000a905aff9d8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
KERNEL32!WriteConsoleW:
00007ffb`a97028f0 ff258a4c0500    jmp     qword ptr [KERNEL32!_imp_WriteConsoleW (00007ffb`a9757580)] ds:00007ffb`a9757580={KERNELBASE!WriteConsoleW (00007ffb`a697b750)}

我尝试改用 CoInitializeEx 并遇到同样的问题:

mov     edx, COINIT_APARTMENTTHREADED   ; dwCoInit (COINIT_APARTMENTTHREADED = 2)
xor     ecx, ecx                        ; pvReserved
call    CoInitializeEx
4

1 回答 1

1

x64 ABI 要求在执行调用指令时堆栈始终为 16 字节对齐,同时保留 32 字节空间。所以在每个函数入口点我们都会有:

RSP == 16*N + 8

所以我们一般必须SUB RSP,40 + N*16在函数体中做,如果我们要调用另一个函数。但是当我们 LOCAL在函数中声明变量时 - 编译器(masm64)会进行一些堆栈分配,但不会进行堆栈对齐和 32 字节保留空间。所以需要自己做。当您使用LOCAL变量时 - masm64 使用RBP寄存器保存旧RSP值并在最后恢复它(使用leave指令)。并通过访问本地人,RBP因此您不能自己更改RBP功能。

所以代码可以是下一个

STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12

EXTRN   __imp_GetStdHandle:QWORD
EXTRN   __imp_WriteConsoleW:QWORD
EXTRN   __imp_CoInitialize:QWORD
EXTRN   __imp_ExitProcess:QWORD
EXTRN   __imp__getch:QWORD
EXTRN   __imp_wcslen:QWORD

WSTRING macro text:VARARG
    FOR arg, <text>
        if @InStr( , arg, @ )
            f = 0
            FORC c,  <arg>
                IF f
                    DW '&c'
                ENDIF
                f = 1
            ENDM
        else
            DW &arg
        endif
    ENDM
    DW 0
ENDM

.const
    strErrComFailed: WSTRING @----, 13, 10, @Press any key:, 13, 10

.code

maina PROC
    LOCAL hStdOutput:QWORD
    LOCAL hErrOutput:QWORD
    LOCAL hResult:DWORD

    sub     rsp,32
    and     rsp,not 15
    xor     ecx,ecx
    call    __imp_CoInitialize
    mov     hResult, eax

    mov     ecx, STD_OUTPUT_HANDLE
    call    __imp_GetStdHandle
    mov     hStdOutput, rax

    mov     ecx, STD_ERROR_HANDLE
    call    __imp_GetStdHandle
    mov     hErrOutput, rax

    lea     rcx,strErrComFailed
    call    __imp_wcslen
    mov     QWORD PTR [rsp+32], 0
    xor     r9d, r9d    ; lpNumberOfCharsWritten
    mov     r8d, eax    ; nNumberOfCharsToWrite
    lea     rdx,strErrComFailed
    mov     rcx,hStdOutput
    call    __imp_WriteConsoleW

    call    __imp__getch

    mov     ecx, eax
    call    __imp_ExitProcess
    ret 

maina ENDP

END

还有一些注意事项:


导入函数总是通过指针调用。所有这些指针名称都从__imp_前缀开始。所以我们需要将导入的 api 声明XxxEXTRN __imp_Xxx:QWORD - 这种更有效的比较PROC声明 - 在这种情况下,链接器需要使用单个 jmp 指令构建存根 proc:

Xxx proc
    jmp __imp_Xxx
Xxx endp

当然更好的是直接call __imp_Xxx而不是Xxx存根,而不是call Xxx- 代码会更小更快


您不需要wcslen自己实现 - 您可以从ntdllp.lib(总是)或msvcrt.lib(非常依赖于具体的 lib 实现,但msvcrt.dll当然导出wcslen- 您可以自己构建msvcrt.lib)导入它并使用call __imp_wcslen


使用声明,如

strErrComFailed         dw 'C','O','M',' '...

很不舒服。例如,您可以使用这样的宏

    WSTRING macro text:VARARG
        FOR arg, <text>
            if @InStr( , arg, @ )
                f = 0
                FORC c,  <arg>
                    IF f
                        DW '&c'
                    ENDIF
                    f = 1
                ENDM
            else
                DW &arg
            endif
        ENDM
        DW 0
    ENDM


  ;strErrComFailed: WSTRING @----, 13, 10, @Press any key:, 13, 10

最后lpNumberOfCharsWritten,函数中的参数WriteConsoleW是可选的。如果您查找声明是sdk - 它用__out_optor声明_Out_opt_。因此,如果不需要此类信息,您可以在此处传递 0

于 2020-01-11T15:43:56.570 回答