为什么 WriteConsoleW 在使用 ml64 调用 CoInitialize 后中断
Why WriteConsoleW breaks After call CoInitialize using ml64
我正在尝试使用 Visual Studio 2019 年的 ml64.exe 通过 64 位程序集执行一些 Office 自动化。在调用 Office COM 接口之前,我需要调用 CoInitialize。我目前只是测试初始化 COM 并写入控制台(我通常不编写汇编代码)如果我注释掉行
call CoInitialize
WriteConsoleW api 调用按预期工作并向屏幕输出消息 "COM Failed to Initialize" 但是,一旦我添加调用 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
x64 ABI 要求 执行调用指令时堆栈总是 16 字节对齐 也保留 32 字节 space。所以在每个函数入口点我们都会有:
RSP == 16*N + 8
所以我们通常必须在函数体中做SUB RSP,40 + N*16
,如果我们要调用另一个函数。
但是当我们在函数中声明 LOCAL
变量时——编译器 (masm64) 会进行一些堆栈分配,但不会执行堆栈对齐和 32 字节保留 space。所以需要自己做。同样,当您使用 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 Xxx
声明为 EXTRN __imp_Xxx:QWORD
- 这种更有效的比较 PROC
声明 - 在这种情况下,链接器需要使用单个 jmp 指令构建存根过程:
Xxx proc
jmp __imp_Xxx
Xxx endp
当然最好直接 call __imp_Xxx
而不是 Xxx
存根,而是 call Xxx
- 代码会更小更快
您不需要自己实现 wcslen
- 您可以从 ntdllp.lib
(总是)或 msvcrt.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
最后,函数 WriteConsoleW
中的 lpNumberOfCharsWritten
参数是可选的。如果您寻找声明是 sdk - 它是用 __out_opt
或 _Out_opt_
声明的。所以如果不需要这样的信息,你可以在这里传递 0
我正在尝试使用 Visual Studio 2019 年的 ml64.exe 通过 64 位程序集执行一些 Office 自动化。在调用 Office COM 接口之前,我需要调用 CoInitialize。我目前只是测试初始化 COM 并写入控制台(我通常不编写汇编代码)如果我注释掉行
call CoInitialize
WriteConsoleW api 调用按预期工作并向屏幕输出消息 "COM Failed to Initialize" 但是,一旦我添加调用 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
x64 ABI 要求 执行调用指令时堆栈总是 16 字节对齐 也保留 32 字节 space。所以在每个函数入口点我们都会有:
RSP == 16*N + 8
所以我们通常必须在函数体中做SUB RSP,40 + N*16
,如果我们要调用另一个函数。
但是当我们在函数中声明 LOCAL
变量时——编译器 (masm64) 会进行一些堆栈分配,但不会执行堆栈对齐和 32 字节保留 space。所以需要自己做。同样,当您使用 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 Xxx
声明为 EXTRN __imp_Xxx:QWORD
- 这种更有效的比较 PROC
声明 - 在这种情况下,链接器需要使用单个 jmp 指令构建存根过程:
Xxx proc
jmp __imp_Xxx
Xxx endp
当然最好直接 call __imp_Xxx
而不是 Xxx
存根,而是 call Xxx
- 代码会更小更快
您不需要自己实现 wcslen
- 您可以从 ntdllp.lib
(总是)或 msvcrt.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
最后,函数 WriteConsoleW
中的 lpNumberOfCharsWritten
参数是可选的。如果您寻找声明是 sdk - 它是用 __out_opt
或 _Out_opt_
声明的。所以如果不需要这样的信息,你可以在这里传递 0