http://bugs.winehq.org/show_bug.cgi?id=13516
Anastasius Focht focht@gmx.net changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |focht@gmx.net
--- Comment #10 from Anastasius Focht focht@gmx.net 2010-05-27 10:48:39 --- Hello,
whenever I encounter vb6 apps I despise Microsoft ... I was tortured years ago, forced to audit/review/reverse various vb apps :-|
That VB6 app in question is protected with tElock 0.981b. After getting rid of this minor nuisance chances got better analysing/debugging vb junk code ;-)
The bug in Wine is actually unearthed by some kind of unicode subclass wrapper/framework for VB6 forms. Basically junk^H^H^H^Hthunk code gets written and patched in dynamic memory at runtime to provide subclassing/hooking facility.
I provide an annotated app code snippet to illustrate the idea how thunks get written and to understand the Wine traces:
--- snip --- ... mov edx, 004CB67Ch ; thunk opcode snippet string "5589E583C4F85731C08945FC8945F8EB0EE8xxxxx01x83F802742185C07424E830000000837DF800750AE838000000E84D0000005F8B45FCC9C21000E826000000EBF168xxxxx02x6AFCFF7508E8xxxxx03xEBE031D24ABFxxxxx04xB9xxxxx05xE82D000000C3FF7514FF7510FF750CFF750868xxxxx06xE8xxxxx07x8945FCC331D2BFxxxxx08xB9xxxxx09xE801000000C3E32F09C978078B450CF2AF75248D4514508D4510508D450C508D4508508D45FC508D45F85052B8xxxxx0Ax508B00FF501CC3" ; "xxxx" will be patched later (jmp/call destinations) ... call MSVBVM60.DLL.__vbaStrCopy ... call [004010C4h] ; Len(arg) mov edi, MSVBVM60.DLL.__vbaStrMove ... opcode_bstr_loop:
cmp ebx, var_1C ; all opcodes copied? jnle opcode_bstr_loop_end ... push 004ADF0Ch ; "&H" push ecx push ebx push edx call [00401210h] ; Mid$(arg1, arg2, arg3) mov edx, eax lea ecx, var_28 call edi push eax call [00401118h] ; operator "&" ... push eax call [0040150Ch] ; Val(arg) call MSVBVM60.DLL.__vbaFpUI1 push eax call MSVBVM60.DLL.rtcBstrFromByte ... call [00401118h] ; operator "&" ... jmp opcode_bstr_loop
opcode_bstr_loop_end: ... push eax call [00401138h] ; LenB(arg) mov edi, eax push 00000040h push 00003000h push edi ; length of opcode snippet (thunk) in bytes push 00000000h mov [esi+50h], edi VirtualAlloc(%x1, %x2, %x3, %x4) mov var_58, eax call MSVBVM60.DLL.__vbaSetSystemError ... call [00401398h] ; VarPtr(arg) push edi mov edi, CopyMemory(%x1, %x2, %x3) mov var_58, eax push eax mov eax, [esi+48h] push eax call edi ; copy BSTR thunk code snippet to final location ... --- snip ---
With that annotated code snippet in mind we can now make a meaning of corresponding Wine traces:
0xC3 (ret) is the last opcode (iteration) from that specific thunk snippet.
+tid,+seh,+relay,+ole,+variant
--- snip --- 0046:Call oleaut32.VarBstrCat(004adf0c L"&H",0016fcb4 L"C3",0032eb20) ret=733867fd ... 0046:Call oleaut32.VarParseNumFromStr(0016f6ec L"&HC3",00000409,80000000,0032eadc,0032eabc) ret=73373408 ... 0046:Call oleaut32.VariantChangeType(0032eb0c,0032eb0c,00000000,00000005) ret=733734a9 0046:trace:variant:VariantChangeTypeEx (0x32eb0c->(VT_I4),0x32eb0c->(VT_I4),0x00000400,0x0000,VT_R8) ... 0046:Call oleaut32.SysAllocStringByteLen(0032eb24,00000001) ret=73380bf5 0046:Call ntdll.RtlAllocateHeap(00110000,00000000,00000007) ret=68b29e68 0046:Ret ntdll.RtlAllocateHeap() retval=0016f6e8 ret=68b29e68 0046:Ret oleaut32.SysAllocStringByteLen() retval=0016f6ec ret=73380bf5 0046:Call oleaut32.VarBstrCat(0016fce4 L"",0016f6ec L"\00c3",0032eb24) ret=733867fd 0046:trace:variant:VarBstrCat L"",L"",0x32eb24 0046:Call ntdll.RtlAllocateHeap(00110000,00000000,00000006) ret=68b29f0b 0046:Ret ntdll.RtlAllocateHeap() retval=0016f700 ret=68b29f0b 0046:trace:variant:VarBstrCat L"" 0046:Ret oleaut32.VarBstrCat() retval=00000000 ret=733867fd 0046:Call oleaut32.SysFreeString(0016fce4 L"") ret=733869b7 ... --- snip ---
After the binary opcode snippet is stuffed into a BSTR it should get written out to memory... Unfortunately the thunk code allocator is called with zero length:
--- snip --- 0046:Call KERNEL32.VirtualAlloc(00000000,00000000,00003000,00000040) ret=00aa1b04 0046:Ret KERNEL32.VirtualAlloc() retval=00000000 ret=00aa1b04 0046:Call KERNEL32.GetLastError() ret=7336c0c7 0046:Ret KERNEL32.GetLastError() retval=00000057 ret=7336c0c7 0046:Call KERNEL32.TlsGetValue(00000005) ret=7336c0d5 0046:Ret KERNEL32.TlsGetValue() retval=0013d280 ret=7336c0d5 0046:Call ntdll.RtlMoveMemory(00000000,0016f704,00000000) ret=00aa1b2e 0046:Ret ntdll.RtlMoveMemory() retval=00000000 ret=00aa1b2e --- snip ---
When the jmp/call destinations contained in that thunk are to be resolved, the app crashes:
--- snip --- ... 0046:Call ntdll.RtlMoveMemory(0000004e,0032eb20,00000004) ret=00aa245a 0046:trace:seh:raise_exception code=c0000005 flags=0 addr=0x681f2043 ip=681f2043 tid=0046 0046:trace:seh:raise_exception info[0]=00000001 0046:trace:seh:raise_exception info[1]=0000004e 0046:trace:seh:raise_exception eax=ffcd152e ebx=7bc98440 ecx=00000001 edx=0032eb1c esi=0032eb20 edi=0000004e 0046:trace:seh:raise_exception ebp=0032ea68 esp=0032ea48 cs=0023 ds=002b es=002b fs=0063 gs=006b flags=00010202 --- snip ---
dest ptr = 0x4e is the offset from the thunks block base address (base should be 0x02220000 in this example but due to previous failure it returned NULL). Thunk code to be patched:
--- snip --- 02220000 55 PUSH EBP ... 02220043 68 00000002 PUSH 2000000 02220048 6A FC PUSH -4 0222004A FF75 08 PUSH DWORD PTR SS:[EBP+8] 0222004D E8 00000003 CALL 05220052 ... --- snip ---
The final BSTR containing the opcodes is actually empty, so the previous concatenations failed.
The culprit in Wine seems to be a misunderstanding what BSTR really are and the relation to WCHAR*.
This app (mis)uses BSTRs to not only store/move unicode strings but to also move binary data around (like thunk opcodes). That means the BSTR _can_ actually contain an odd number of bytes - simple case: the opcode loop which converts each opcode byte to 1-byte BSTR and concats them into a binary (byte) array -> BSTR.
Unfortunately Wine code like oleaut32's VarBstrCat() deals with BSTRs as they were true UNICODE strings.
--- snip dlls/oleaut32/vartype.c ---
HRESULT WINAPI VarBstrCat(BSTR pbstrLeft, BSTR pbstrRight, BSTR *pbstrOut) { unsigned int lenLeft, lenRight;
TRACE("%s,%s,%p\n", debugstr_wn(pbstrLeft, SysStringLen(pbstrLeft)), debugstr_wn(pbstrRight, SysStringLen(pbstrRight)), pbstrOut);
if (!pbstrOut) return E_INVALIDARG;
lenLeft = pbstrLeft ? SysStringLen(pbstrLeft) : 0; lenRight = pbstrRight ? SysStringLen(pbstrRight) : 0;
*pbstrOut = SysAllocStringLen(NULL, lenLeft + lenRight); if (!*pbstrOut) return E_OUTOFMEMORY;
(*pbstrOut)[0] = '\0';
if (pbstrLeft) memcpy(*pbstrOut, pbstrLeft, lenLeft * sizeof(WCHAR));
if (pbstrRight) memcpy(*pbstrOut + lenLeft, pbstrRight, lenRight * sizeof(WCHAR));
TRACE("%s\n", debugstr_wn(*pbstrOut, SysStringLen(*pbstrOut))); return S_OK; }
--- snip dlls/oleaut32/vartype.c ---
That means incorrect behaviour if for example "ansi" BSTRs with odd length are passed (SysStringLen() determines the length in wide-characters).
Make sure the test suite contains such cases and passes to not break other stuff ;-)
Regards