A nasty bug in MSVCRT.DLL

(The note below has been copypasted to the Reverse Engineering for Beginners book.)

This is the bug that costed me several hours of debugging.

In 2013 I was using MinGW, my C project seems to be very unstable and I saw the "Invalid parameter passed to C runtime function." error message in debugger.

The error message was also visible using Sysinternals DebugView. And my project has no such error messages or strings. So I started to search it in the whole Windows and found in MSVCRT.DLL file. (Needless to say I was using Windows 7.)

So here it is, the error message in MSVCRT.DLL file supplied with Windows 7:

.text:6FFB69D0 OutputString    db 'Invalid parameter passed to C runtime function.',0Ah,0
.text:6FFB69D0                                         ; DATA XREF: sub_6FFB6930+83o

Where it is referenced?

.text:6FFB6930 sub_6FFB6930    proc near               ; CODE XREF: _wfindfirst64+203FCp
.text:6FFB6930                                         ; sub_6FF62563+319ADp ...
.text:6FFB6930
.text:6FFB6930 var_2D0         = dword ptr -2D0h
.text:6FFB6930 var_244         = word ptr -244h
.text:6FFB6930 var_240         = word ptr -240h
.text:6FFB6930 var_23C         = word ptr -23Ch
.text:6FFB6930 var_238         = word ptr -238h
.text:6FFB6930 var_234         = dword ptr -234h
.text:6FFB6930 var_230         = dword ptr -230h
.text:6FFB6930 var_22C         = dword ptr -22Ch
.text:6FFB6930 var_228         = dword ptr -228h
.text:6FFB6930 var_224         = dword ptr -224h
.text:6FFB6930 var_220         = dword ptr -220h
.text:6FFB6930 var_21C         = dword ptr -21Ch
.text:6FFB6930 var_218         = dword ptr -218h
.text:6FFB6930 var_214         = word ptr -214h
.text:6FFB6930 var_210         = dword ptr -210h
.text:6FFB6930 var_20C         = dword ptr -20Ch
.text:6FFB6930 var_208         = word ptr -208h
.text:6FFB6930 var_4           = dword ptr -4
.text:6FFB6930
.text:6FFB6930                 mov     edi, edi
.text:6FFB6932                 push    ebp
.text:6FFB6933                 mov     ebp, esp
.text:6FFB6935                 sub     esp, 2D0h
.text:6FFB693B                 mov     eax, ___security_cookie
.text:6FFB6940                 xor     eax, ebp
.text:6FFB6942                 mov     [ebp+var_4], eax
.text:6FFB6945                 mov     [ebp+var_220], eax
.text:6FFB694B                 mov     [ebp+var_224], ecx
.text:6FFB6951                 mov     [ebp+var_228], edx
.text:6FFB6957                 mov     [ebp+var_22C], ebx
.text:6FFB695D                 mov     [ebp+var_230], esi
.text:6FFB6963                 mov     [ebp+var_234], edi
.text:6FFB6969                 mov     [ebp+var_208], ss
.text:6FFB696F                 mov     [ebp+var_214], cs
.text:6FFB6975                 mov     [ebp+var_238], ds
.text:6FFB697B                 mov     [ebp+var_23C], es
.text:6FFB6981                 mov     [ebp+var_240], fs
.text:6FFB6987                 mov     [ebp+var_244], gs
.text:6FFB698D                 pushf
.text:6FFB698E                 pop     [ebp+var_210]
.text:6FFB6994                 mov     eax, [ebp+4]
.text:6FFB6997                 mov     [ebp+var_218], eax
.text:6FFB699D                 lea     eax, [ebp+4]
.text:6FFB69A0                 mov     [ebp+var_2D0], 10001h
.text:6FFB69AA                 mov     [ebp+var_20C], eax
.text:6FFB69B0                 mov     eax, [eax-4]
.text:6FFB69B3                 push    offset OutputString ; "Invalid parameter passed to C runtime f"...
.text:6FFB69B8                 mov     [ebp+var_21C], eax
.text:6FFB69BE                 call    ds:OutputDebugStringA
.text:6FFB69C4                 mov     ecx, [ebp+var_4]
.text:6FFB69C7                 xor     ecx, ebp
.text:6FFB69C9                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:6FFB69CE                 leave
.text:6FFB69CF                 retn
.text:6FFB69CF sub_6FFB6930    endp

The string it reported into debugger or DebugView utility using the standard OutputDebugStringA() funciton. How the sub_6FFB6930 can be called? IDA shows at least 280 references.

Using my tracer utility, I set a breakpoint at sub_6FFB6930 to see, when it's called in my case:

tracer.exe -l:1.exe bpf=msvcrt.dll!0x6FFB6930 -s

...

PID=3560|New process 1.exe
(0) msvcrt.dll!0x6ffb6930() (called from msvcrt.dll!_ftol2_sse_excpt+0x1b467 (0x759ed222))
Call stack:
return address=0x401010 (1.exe!.text+0x10), arguments in stack: 0x12ff14, 0x401010, 0x403010("asd"), 0x0, 0x12ff88, 0x4010f8
return address=0x4010f8 (1.exe!OEP+0xe3), arguments in stack: 0x12ff88, 0x4010f8, 0x1, 0x2e0ea8, 0x2e1640, 0x403000
return address=0x75b6ef3c (KERNEL32.dll!BaseThreadInitThunk+0x12), arguments in stack: 0x12ff94, 0x75b6ef3c, 0x7ffdf000, 0x12ffd4, 0x77523688, 0x7ffdf000
return address=0x77523688 (ntdll.dll!RtlInitializeExceptionChain+0xef), arguments in stack: 0x12ffd4, 0x77523688, 0x7ffdf000, 0x74117ec7, 0x0, 0x0
return address=0x7752365b (ntdll.dll!RtlInitializeExceptionChain+0xc2), arguments in stack: 0x12ffec, 0x7752365b, 0x401015, 0x7ffdf000, 0x0, 0x0
(0) msvcrt.dll!0x6ffb6930() -> 0x12f94c
PID=3560|Process 1.exe exited. ExitCode=2147483647 (0x7fffffff)

I found that my code was calling stricmp() function with NULL as one argument. In fact, I made up this example when writing this:

#include <stdio.h>
#include <string.h>

int main()
{
	stricmp ("asd", NULL);
};

If this piece of code is compiled using old MinGW or old MSVC 6.0, it is linked against MSVCRT.DLL file. Which, as of Windows 7, silently sends the "Invalid parameter passed to C runtime function." error message to the debugger and then does nothing!

Let's see how stricmp() is implemented in MSVCRT.DLL:

.text:6FF5DB38 ; Exported entry 855. _strcmpi
.text:6FF5DB38 ; Exported entry 863. _stricmp
.text:6FF5DB38
.text:6FF5DB38 ; =============== S U B R O U T I N E =======================================
.text:6FF5DB38
.text:6FF5DB38 ; Attributes: bp-based frame
.text:6FF5DB38
.text:6FF5DB38 ; int __cdecl strcmpi(const char *, const char *)
.text:6FF5DB38                 public _strcmpi
.text:6FF5DB38 _strcmpi        proc near               ; CODE XREF: LocaleEnumProc-2Bp
.text:6FF5DB38                                         ; LocaleEnumProc+5Ep ...
.text:6FF5DB38
.text:6FF5DB38 arg_0           = dword ptr  8
.text:6FF5DB38 arg_4           = dword ptr  0Ch
.text:6FF5DB38
.text:6FF5DB38 ; FUNCTION CHUNK AT .text:6FF68CFD SIZE 00000012 BYTES
.text:6FF5DB38 ; FUNCTION CHUNK AT .text:6FF9D20D SIZE 00000022 BYTES
.text:6FF5DB38
.text:6FF5DB38                 mov     edi, edi        ; _strcmpi
.text:6FF5DB3A                 push    ebp
.text:6FF5DB3B                 mov     ebp, esp
.text:6FF5DB3D                 push    esi
.text:6FF5DB3E                 xor     esi, esi
.text:6FF5DB40                 cmp     dword_6FFF0000, esi
.text:6FF5DB46                 jnz     loc_6FF68CFD
.text:6FF5DB4C                 cmp     [ebp+arg_0], esi  ; is arg_0==NULL?
.text:6FF5DB4F                 jz      loc_6FF9D20D
.text:6FF5DB55                 cmp     [ebp+arg_4], esi  ; is arg_0==NULL?
.text:6FF5DB58                 jz      loc_6FF9D20D
.text:6FF5DB5E                 pop     esi
.text:6FF5DB5F                 pop     ebp
.text:6FF5DB5F _strcmpi        endp ; sp-analysis failed

Actual strings comparison here:

.text:6FF5DB60 sub_6FF5DB60    proc near               ; CODE XREF: _stricmp_l+16C7Fp
.text:6FF5DB60                                         ; sub_6FFD19CD+229p ...
.text:6FF5DB60
.text:6FF5DB60 arg_0           = dword ptr  8
.text:6FF5DB60 arg_4           = dword ptr  0Ch
.text:6FF5DB60
.text:6FF5DB60                 push    ebp
.text:6FF5DB61                 mov     ebp, esp
.text:6FF5DB63                 push    edi
.text:6FF5DB64                 push    esi
.text:6FF5DB65                 push    ebx
.text:6FF5DB66                 mov     esi, [ebp+arg_4]
.text:6FF5DB69                 mov     edi, [ebp+arg_0]
.text:6FF5DB6C                 mov     al, 0FFh
.text:6FF5DB6E                 mov     edi, edi
.text:6FF5DB70
.text:6FF5DB70 loc_6FF5DB70:                           ; CODE XREF: sub_6FF5DB60+20j
.text:6FF5DB70                                         ; sub_6FF5DB60+40j
.text:6FF5DB70                 or      al, al
.text:6FF5DB72                 jz      short loc_6FF5DBA6
.text:6FF5DB74                 mov     al, [esi]
.text:6FF5DB76                 add     esi, 1
.text:6FF5DB79                 mov     ah, [edi]
.text:6FF5DB7B                 add     edi, 1
.text:6FF5DB7E                 cmp     ah, al
.text:6FF5DB80                 jz      short loc_6FF5DB70
.text:6FF5DB82                 sub     al, 41h
.text:6FF5DB84                 cmp     al, 1Ah
.text:6FF5DB86                 sbb     cl, cl
.text:6FF5DB88                 and     cl, 20h
.text:6FF5DB8B                 add     al, cl
.text:6FF5DB8D                 add     al, 41h
.text:6FF5DB8F                 xchg    ah, al
.text:6FF5DB91                 sub     al, 41h
.text:6FF5DB93                 cmp     al, 1Ah
.text:6FF5DB95                 sbb     cl, cl
.text:6FF5DB97                 and     cl, 20h
.text:6FF5DB9A                 add     al, cl
.text:6FF5DB9C                 add     al, 41h
.text:6FF5DB9E                 cmp     al, ah
.text:6FF5DBA0                 jz      short loc_6FF5DB70
.text:6FF5DBA2                 sbb     al, al
.text:6FF5DBA4                 sbb     al, 0FFh
.text:6FF5DBA6
.text:6FF5DBA6 loc_6FF5DBA6:                           ; CODE XREF: sub_6FF5DB60+12j
.text:6FF5DBA6                 movsx   eax, al
.text:6FF5DBA9                 pop     ebx
.text:6FF5DBAA                 pop     esi
.text:6FF5DBAB                 pop     edi
.text:6FF5DBAC                 leave
.text:6FF5DBAD                 retn
.text:6FF5DBAD sub_6FF5DB60    endp

.text:6FF68D0C loc_6FF68D0C:                           ; CODE XREF: _strcmpi+3F6F2j
.text:6FF68D0C                 pop     esi
.text:6FF68D0D                 pop     ebp
.text:6FF68D0E                 retn

.text:6FF9D20D loc_6FF9D20D:                           ; CODE XREF: _strcmpi+17j
.text:6FF9D20D                                         ; _strcmpi+20j
.text:6FF9D20D                 call    near ptr _errno
.text:6FF9D212                 push    esi
.text:6FF9D213                 push    esi
.text:6FF9D214                 push    esi
.text:6FF9D215                 push    esi
.text:6FF9D216                 push    esi
.text:6FF9D217                 mov     dword ptr [eax], 16h
.text:6FF9D21D                 call    _invalid_parameter
.text:6FF9D222                 add     esp, 14h
.text:6FF9D225                 mov     eax, 7FFFFFFFh
.text:6FF9D22A                 jmp     loc_6FF68D0C

Now the invalid_parameter() function:

.text:6FFB6A06                 public _invalid_parameter
.text:6FFB6A06 _invalid_parameter proc near            ; CODE XREF: sub_6FF5B494:loc_6FF5B618p
.text:6FFB6A06                                         ; sub_6FF5CCFD:loc_6FF5C8A2p ...
.text:6FFB6A06                 mov     edi, edi
.text:6FFB6A08                 push    ebp
.text:6FFB6A09                 mov     ebp, esp
.text:6FFB6A0B                 pop     ebp
.text:6FFB6A0C                 jmp     sub_6FFB6930
.text:6FFB6A0C _invalid_parameter endp

.text:6FFB6930 sub_6FFB6930    proc near               ; CODE XREF: _wfindfirst64+203FCp
.text:6FFB6930                                         ; sub_6FF62563+319ADp ...
.text:6FFB6930
.text:6FFB6930 var_2D0         = dword ptr -2D0h
.text:6FFB6930 var_244         = word ptr -244h
.text:6FFB6930 var_240         = word ptr -240h
.text:6FFB6930 var_23C         = word ptr -23Ch
.text:6FFB6930 var_238         = word ptr -238h
.text:6FFB6930 var_234         = dword ptr -234h
.text:6FFB6930 var_230         = dword ptr -230h
.text:6FFB6930 var_22C         = dword ptr -22Ch
.text:6FFB6930 var_228         = dword ptr -228h
.text:6FFB6930 var_224         = dword ptr -224h
.text:6FFB6930 var_220         = dword ptr -220h
.text:6FFB6930 var_21C         = dword ptr -21Ch
.text:6FFB6930 var_218         = dword ptr -218h
.text:6FFB6930 var_214         = word ptr -214h
.text:6FFB6930 var_210         = dword ptr -210h
.text:6FFB6930 var_20C         = dword ptr -20Ch
.text:6FFB6930 var_208         = word ptr -208h
.text:6FFB6930 var_4           = dword ptr -4
.text:6FFB6930
.text:6FFB6930                 mov     edi, edi
.text:6FFB6932                 push    ebp
.text:6FFB6933                 mov     ebp, esp
.text:6FFB6935                 sub     esp, 2D0h
.text:6FFB693B                 mov     eax, ___security_cookie
.text:6FFB6940                 xor     eax, ebp
.text:6FFB6942                 mov     [ebp+var_4], eax
.text:6FFB6945                 mov     [ebp+var_220], eax
.text:6FFB694B                 mov     [ebp+var_224], ecx
.text:6FFB6951                 mov     [ebp+var_228], edx
.text:6FFB6957                 mov     [ebp+var_22C], ebx
.text:6FFB695D                 mov     [ebp+var_230], esi
.text:6FFB6963                 mov     [ebp+var_234], edi
.text:6FFB6969                 mov     [ebp+var_208], ss
.text:6FFB696F                 mov     [ebp+var_214], cs
.text:6FFB6975                 mov     [ebp+var_238], ds
.text:6FFB697B                 mov     [ebp+var_23C], es
.text:6FFB6981                 mov     [ebp+var_240], fs
.text:6FFB6987                 mov     [ebp+var_244], gs
.text:6FFB698D                 pushf
.text:6FFB698E                 pop     [ebp+var_210]
.text:6FFB6994                 mov     eax, [ebp+4]
.text:6FFB6997                 mov     [ebp+var_218], eax
.text:6FFB699D                 lea     eax, [ebp+4]
.text:6FFB69A0                 mov     [ebp+var_2D0], 10001h
.text:6FFB69AA                 mov     [ebp+var_20C], eax
.text:6FFB69B0                 mov     eax, [eax-4]
.text:6FFB69B3                 push    offset OutputString ; "Invalid parameter passed to C runtime f"...
.text:6FFB69B8                 mov     [ebp+var_21C], eax
.text:6FFB69BE                 call    ds:OutputDebugStringA
.text:6FFB69C4                 mov     ecx, [ebp+var_4]
.text:6FFB69C7                 xor     ecx, ebp
.text:6FFB69C9                 call    @__security_check_cookie@4 ; __security_check_cookie(x)
.text:6FFB69CE                 leave
.text:6FFB69CF                 retn
.text:6FFB69CF sub_6FFB6930    endp

You see, the stricmp() code is like:

int stricmp(const char *s1, const char *s2, size_t len)
{
	if (s1==NULL || s2==NULL)
	{
		// print error message AND exit:
		return 0x7FFFFFFFh;
	};
	// do comparison
};

How come this error is rare? Because newer MSVC versions links against MSVCR120.DLL file, etc (where 120 is version number).

Let's peek inside the newer MSVCR120.DLL from Windows 7:

.text:1002A0D4                 public _stricmp_l
.text:1002A0D4 _stricmp_l      proc near               ; CODE XREF: _stricmp+18p
.text:1002A0D4                                         ; _mbsicmp_l+47p
.text:1002A0D4                                         ; DATA XREF: ...
.text:1002A0D4
.text:1002A0D4 var_10          = dword ptr -10h
.text:1002A0D4 var_8           = dword ptr -8
.text:1002A0D4 var_4           = byte ptr -4
.text:1002A0D4 arg_0           = dword ptr  8
.text:1002A0D4 arg_4           = dword ptr  0Ch
.text:1002A0D4 arg_8           = dword ptr  10h
.text:1002A0D4
.text:1002A0D4 ; FUNCTION CHUNK AT .text:1005AA7B SIZE 0000002A BYTES
.text:1002A0D4
.text:1002A0D4                 push    ebp
.text:1002A0D5                 mov     ebp, esp
.text:1002A0D7                 sub     esp, 10h
.text:1002A0DA                 lea     ecx, [ebp+var_10]
.text:1002A0DD                 push    ebx
.text:1002A0DE                 push    esi
.text:1002A0DF                 push    edi
.text:1002A0E0                 push    [ebp+arg_8]
.text:1002A0E3                 call    sub_1000F764
.text:1002A0E8                 mov     edi, [ebp+arg_0] ; arg==NULL?
.text:1002A0EB                 test    edi, edi
.text:1002A0ED                 jz      loc_1005AA7B
.text:1002A0F3                 mov     ebx, [ebp+arg_4] ; arg==NULL?
.text:1002A0F6                 test    ebx, ebx
.text:1002A0F8                 jz      loc_1005AA7B
.text:1002A0FE                 mov     eax, [ebp+var_10]
.text:1002A101                 cmp     dword ptr [eax+0A8h], 0
.text:1002A108                 jz      loc_1005AA95
.text:1002A10E                 sub     edi, ebx

...

.text:1005AA7B loc_1005AA7B:                           ; CODE XREF: _stricmp_l+19j
.text:1005AA7B                                         ; _stricmp_l+24j
.text:1005AA7B                 call    _errno
.text:1005AA80                 mov     dword ptr [eax], 16h
.text:1005AA86                 call    _invalid_parameter_noinfo
.text:1005AA8B                 mov     esi, 7FFFFFFFh
.text:1005AA90                 jmp     loc_1002A13B

...

.text:100A4670 _invalid_parameter_noinfo proc near     ; CODE XREF: sub_10013BEC-10Fp
.text:100A4670                                         ; sub_10016C0F-10Fp ...
.text:100A4670                 xor     eax, eax
.text:100A4672                 push    eax
.text:100A4673                 push    eax
.text:100A4674                 push    eax
.text:100A4675                 push    eax
.text:100A4676                 push    eax
.text:100A4677                 call    _invalid_parameter
.text:100A467C                 add     esp, 14h
.text:100A467F                 retn
.text:100A467F _invalid_parameter_noinfo endp

...

.text:100A4645 _invalid_parameter proc near            ; CODE XREF: _invalid_parameter(ushort const *,ushort const *,ushort const *,uint,uint)j
.text:100A4645                                         ; _invalid_parameter_noinfo+7p ...
.text:100A4645
.text:100A4645 arg_0           = dword ptr  8
.text:100A4645 arg_4           = dword ptr  0Ch
.text:100A4645 arg_8           = dword ptr  10h
.text:100A4645 arg_C           = dword ptr  14h
.text:100A4645 arg_10          = dword ptr  18h
.text:100A4645
.text:100A4645                 push    ebp
.text:100A4646                 mov     ebp, esp
.text:100A4648                 push    dword_100E0ED8  ; Ptr
.text:100A464E                 call    ds:DecodePointer
.text:100A4654                 test    eax, eax
.text:100A4656                 jz      short loc_100A465B
.text:100A4658                 pop     ebp
.text:100A4659                 jmp     eax
.text:100A465B ; ---------------------------------------------------------------------------
.text:100A465B
.text:100A465B loc_100A465B:                           ; CODE XREF: _invalid_parameter+11j
.text:100A465B                 push    [ebp+arg_10]
.text:100A465E                 push    [ebp+arg_C]
.text:100A4661                 push    [ebp+arg_8]
.text:100A4664                 push    [ebp+arg_4]
.text:100A4667                 push    [ebp+arg_0]
.text:100A466A                 call    _invoke_watson
.text:100A466F                 int     3               ; Trap to Debugger
.text:100A466F _invalid_parameter endp

.text:100A469B _invoke_watson  proc near               ; CODE XREF: sub_1002CDB0+27068p
.text:100A469B                                         ; sub_10029704+2A792p ...
.text:100A469B                 push    17h             ; ProcessorFeature
.text:100A469D                 call    IsProcessorFeaturePresent
.text:100A46A2                 test    eax, eax
.text:100A46A4                 jz      short loc_100A46AB
.text:100A46A6                 push    5
.text:100A46A8                 pop     ecx
.text:100A46A9                 int     29h             ; Win8: RtlFailFast(ecx)
.text:100A46AB ; ---------------------------------------------------------------------------
.text:100A46AB
.text:100A46AB loc_100A46AB:                           ; CODE XREF: _invoke_watson+9j
.text:100A46AB                 push    esi
.text:100A46AC                 push    1
.text:100A46AE                 mov     esi, 0C0000417h
.text:100A46B3                 push    esi
.text:100A46B4                 push    2
.text:100A46B6                 call    sub_100A4519
.text:100A46BB                 push    esi             ; uExitCode
.text:100A46BC                 call    __crtTerminateProcess
.text:100A46C1                 add     esp, 10h
.text:100A46C4                 pop     esi
.text:100A46C5                 retn
.text:100A46C5 _invoke_watson  endp

Now the invalid_parameter() function is rewritten in newer MSVCR*.DLL version, it shows the message box, if you want to kill the process or call debugger. Of course, this is much better than silently return. Perhaps, Microsoft forgot to fix MSVCRT.DLL since then.

But how it was working in the era of Windows XP? It wasn't: MSVCRT.DLL from Windows XP doesn't check arguments against NULL. So under Windows XP my "stricmp ("asd", NULL)" code will crash, and this is good.

My hypothesis: Microsoft upgraded MSVCR*.DLL files (including MSVCRT.DLL) for Windows 7 by adding sanitizing checks everywhere. However, since MSVCRT.DLL wasn't used much since MSVS .NET (year 2002), it wasn't properly tested and the bug left here. But compilers like MinGW can still use this DLL.

What would I do without my reverse engineering skills?

The MSVCRT.DLL from Windows 8.1 has the same bug.


→ [list of blog posts]

Please drop me email about any bug(s) and suggestion(s): dennis(@)yurichev.com.