(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 bug(s) and/or suggestion(s): my emails.