News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

A recent Windows 10 build punishes you for your sloppy coding

Started by jj2007, April 10, 2020, 12:26:55 PM

Previous topic - Next topic

jj2007

Special thanks to our new member TioPepperoni, who helped me hunting down a thoroughly hidden bug :thup:

This used to work fine in WinXP, Win7 and Win10:
sometest proc
Local buffer[8000]:BYTE
  lea edi, buffer
  invoke GetWindowTextW, hEdit, edi, 8000
  print edi
  ret
sometest endp


With this recent Windows 10 version, it fails silently - GetLastError reports no error but also no characters copied:
  OS: Windows 10 Pro (x64)
  Version: 1903
  Build: 18362.720

On my Italian OS, Version 1903 Build 18362.592, GetWindowTextW still works fine. My suspicion is that the new OS version checks if the buffer passed is really OK - and it's not, because the "W" wants chars, not bytes. So potentially, if the control has more than 8000 bytes of text, GetWindowTextW would trash the buffer including those precious bytes that allow sometest to return.

Usually, you can allocate slightly less than 3 pages, i.e. 3*4096 bytes as local variables without running into problems. That seems to have changed. You have been warned :badgrin:

K_F

Thinking ?
No chance of your page-size being changed somewhere.
'Sire, Sire!... the peasants are Revolting !!!'
'Yes, they are.. aren't they....'

Biterider

Hi JJ
On my OS I can not reproduce this bug. GetWindowTextW returns the caption as expected (with a short wide string).
No doubt that the 8000 is wrong.

Biterider

jj2007

Hi Biterider,

Strange, because your build number is higher. I suppose Tio's OS is the US English version, mine is Italian, yours is German. What's going on? My suspicion was that the OS either runs into a guard page (but how?), or that it checks explicitly for a buffer overwrite by comparing the start pos against esp/ebp. Unfortunately I can't debug it myself :sad:

Biterider

Hi JJ
Can you provide the test executable that is causing the issue compiled with debug information and the pdb file?
I'll try here to check if the operating system is causing the problem.  :icon_idea:

Biterider

jj2007

Quote from: Biterider on April 11, 2020, 03:41:34 PM
Can you provide the test executable that is causing the issue compiled with debug information and the pdb file?
I'll try here to check if the operating system is causing the problem.  :icon_idea:

Attached, but no pdb file. I see the symbols, though, in Olly. Note you need a current MasmBasic installation to use it. The effect is subtle: When hitting F6, it builds whatever source fine, the exe is there, but on Tio's machine it doesn't launch the exe because GetWindowTextW does not fill the buffer. GetLastError returns zero, GetWindowTextW returns zero chars copied.

The invoke GetWindowTextW is at 40877E

Extract the exe as \Masm32\MasmBasic\ReTestDebug.exe and just launch it. Then go File/New Masm source, pick e.g. a standard Masm32 SDK example from the top and hit F6.

Biterider

Hi JJ
I deleted the complete MASMBASIC folder and reinstalled it using the link you provided.
The installation ended with an error. A dialog was displayed stating that the module msptls.dll is missing.
I closed the dialog and double-clicked the exe from the attachment.
A new dialog with the message that "Res\RichMasmGuide.asc cannot be opened" was displayed.
I closed the dialog and a huge cyan blank window appeared. I pressed F6 and a new dialog with the message "...\Desktop\Res\ bldallRM.bav was not found" was displayed.

Maybe a plain MASM exe will be a better way to test described behavior.  :icon_idea:

Biterider



jj2007

Thanks for the feedback. You might have an incomplete RichEdit installation. Please check if you can find
C:\Program Files (x86)\Common Files\microsoft shared\OFFICE1?\MSPTLS.DLL
If the editor, i.e. \Masm32\MasmBasic\RichMasm.exe, starts at all, press F9 to see the RichEdit version used (in the last two lines). RichMasm checks if the user has any MS Office stuff available, and picks the best RichEd20.dll version, which is Office12 in my case. Office11 is ok, Office14 and higher are somewhat buggy...

> I pressed F6 and a new dialog with the message "...\Desktop\Res\ bldallRM.bav was not found" was displayed
The exe in the archive ReTestDebug.zip cannot run from an arbitrary folder. It must be extracted as \Masm32\MasmBasic\ReTestDebg.exe, otherwise it won't find a dozen little helpers.

Biterider

Hi JJ
I managed to run the application. With Olly I reproduced the behavior you described. GetWindowTextW returned eax = 0.
If I change the argument (buffer size) to 4000, GetWindowTextW returns eax = 0F1h and fills the buffer with this wide string:
Quote** Start D:\MASM32\MasmBasic\Res\bldallRM.bat **
**** 32-bit assembly ****

*** Assemble, link and run RichMasmGuide ***

*** Assemble using UAsm64  ***
Error A2106: Cannot open file: "tmp_file.asm" [ENOENT]
*** Assembly Error ***

The I changed the debugger to VS. Now the API behaves correctly and returns the same string. What I noticed is that the guard page is a few hundert bytes away.
The API may fail because the guard page throws an exception. Try probing the stack before calling GetWindowTextW.

Biterider


jj2007

Quote from: Biterider on April 12, 2020, 07:35:31 AMThe API may fail because the guard page throws an exception.

Thanks for confirming this. Of course, putting 8000 there was a bug.

The interesting aspect here is that apparently they made a tiny little change in the architecture. Despite this bug, it worked fine on WinXP, Win7, and my version of Win10. Now it fails due to the guard pages being set differently.

We might see other software fail, since sloppy coding is not restricted to myself. Adobe, for example, greets me with a "corrected" new version after each and every boot :badgrin:

morgot

Local buffer[8000]:BYTE - is very huge for stack

In my Win10x64 it dont fail.. Or I dont see it.


sometest proc
Local buffer[8000]:BYTE
  call GetShellWindow
  mov ebx,eax
 
  lea edi, buffer
 
  invoke GetWindowTextW, ebx, edi, 8000
  print edi
  ret
sometest endp
end start


type "Program manger"
Sorry for the bad English

hutch--

morgot,

You need to preserve the non volatile registers.

sometest proc
Local buffer[8000]:BYTE

  push ebx
  push edi

  call GetShellWindow
  mov ebx,eax

  lea edi, buffer

  invoke GetWindowTextW, ebx, edi, 8000
  print edi

  pop edi
  pop ebx

  ret
sometest endp
end start

TimoVJL

Quote from: hutch-- on April 22, 2020, 10:35:04 AM

sometest proc
Local buffer[8000]:BYTE
...

  invoke GetWindowTextW, ebx, edi, 8000

buffer size 8000 bytes, no stack check, GetWindowTextW nMaxCount is characters ?
May the source be with you

hutch--

See what I suggested.

You need to preserve the non volatile registers.
......
  push ebx
  push edi
......

  pop edi
  pop ebx

The rest is unmodified.

jj2007

Quote from: TimoVJL on April 22, 2020, 03:46:12 PMbuffer size 8000 bytes, no stack check, GetWindowTextW nMaxCount is characters ?

Exactly. So in the unlikely case that the window has a text that is 4010 characters long, your ret address would be overwritten, and you end up in no man's land on any Windows version. That's why the title has "sloppy coding" in it.

What's new is that in certain*) recent Win10 versions the attempt of GetWindowTextW to write to [esp-8000] fails silently, as explained above.

The mystery here is that it works just fine if you reduce nMaxCount to 4000 chars. I call it a "mystery" because GetWindowTextW still writes to the same address, obviously. So the lack of stack probing is not the explanation. The only rational explanation I have is that GetWindowTextW checks if writing to the end of the buffer would pass by esp:
mov edi, pBuffer
mov ecx, nMaxCount
lea edx, [edi+ecx]
.if edx>esp && edi<esp
  ; invoke SetLastError, YouTrashYourStackIdiot
  xor eax, eax
  ret
.endif


That is technically possible but a) it's too clever a trick for Microsoft coders and b) the SetLastError bit is missing :cool:

Unfortunately I can't test it with a debugger because my (Italian) Win10 still works as before.

*) You have the new version if hitting F6 in the current RichMasm version (here) assembles a hello world but does not run it.