3

I was looking at the NtDll export table on my Windows 10 computer, and I found that it exports standard C runtime functions, like memcpy, sprintf, strlen, etc.

Does that mean that I can call them dynamically at runtime through LoadLibrary and GetProcAddress? Is this guaranteed to be the case for every Windows version?

If so, it is possible to drop the C runtime library altogether (by just using the CRT functions from NtDll), therefore making my program smaller?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Vlad
  • 369
  • 4
  • 16
  • No, you can't do this. Are you really having problems with your executable files being too big? What problem are you trying to solve? – David Heffernan Aug 16 '16 at 16:06
  • 1
    This goes back to very early versions of Windows, back in the 16-bit days when they had to cram a GUI operating system into 640 KB of ram. To give programmers a fighting chance to write small enough programs, Microsoft shared the standard C runtime functions they needed in Windows. They did however stop exposing them in the SDK libs so getting your program linked is not simple. Or wise, the CRT got pretty convoluted. – Hans Passant Aug 16 '16 at 17:32
  • 2
    The CRT is more than just the CRT functions. There's lots and lots and lots of code in the CRT, some of it is clearly visible (exported functions like `memcpy`), other parts hardly make it to the surface (exception handling), while still other parts are widely invisible (like initialization of static storage). Not using the CRT is not at all recommended. If you want small deployment, you can target Windows 10, and use the Universal CRT (which is part of the OS, so you don't have to ship it). – IInspectable Aug 16 '16 at 17:56

3 Answers3

8

There is absolutely no reason to call these undocumented functions exported by NtDll. Windows exports all of the essential C runtime functions as documented wrappers from the standard system libraries, namely Kernel32. If you absolutely cannot link to the C Runtime Library*, then you should be calling these functions. For memory, you have the basic HeapAlloc and HeapFree (or perhaps VirtualAlloc and VirtualFree), ZeroMemory, FillMemory, MoveMemory, CopyMemory, etc. For string manipulation, the important CRT functions are all there, prefixed with an l: lstrlen, lstrcat, lstrcpy, lstrcmp, etc. The odd man out is wsprintf (and its brother wvsprintf), which not only has a different prefix but also doesn't support floating-point values (Windows itself had no floating-point code in the early days when these functions were first exported and documented.) There are a variety of other helper functions, too, that replicate functionality in the CRT, like IsCharLower, CharLower, CharLowerBuff, etc.

Here is an old knowledge base article that documents some of the Win32 Equivalents for C Run-Time Functions. There are likely other relevant Win32 functions that you would probably need if you were re-implementing the functionality of the CRT, but these are the direct, drop-in replacements.

Some of these are absolutely required by the infrastructure of the operating system, and would be called internally by any CRT implementation. This category includes things like HeapAlloc and HeapFree, which are the responsibility of the operating system. A runtime library only wraps those, providing a nice standard-C interface and some other niceties on top of the nitty-gritty OS-level details. Others, like the string manipulation functions, are just exported wrappers around an internal Windows version of the CRT (except that it's a really old version of the CRT, fixed back at some time in history, save for possibly major security holes that have gotten patched over the years). Still others are almost completely superfluous, or seem so, like ZeroMemory and MoveMemory, but are actually exported so that they can be used from environments where there is no C Runtime Library, like classic Visual Basic (VB 6).

It is also interesting to point out that many of the "simple" C Runtime Library functions are implemented by Microsoft's (and other vendors') compiler as intrinsic functions, with special handling. This means that they can be highly optimized. Basically, the relevant object code is emitted inline, directly in your application's binary, avoiding the need for a potentially expensive function call. Allowing the compiler to generate inlined code for something like strlen, that gets called all the time, will almost undoubtedly lead to better performance than having to pay the cost of a function call to one of the exported Windows APIs. There is no way for the compiler to "inline" lstrlen; it gets called just like any other function. This gets you back to the classic tradeoff between speed and size. Sometimes a smaller binary is faster, but sometimes it's not. Not having to link the CRT will produce a smaller binary, since it uses function calls rather than inline implementations, but probably won't produce faster code in the general case.

* However, you really should be linking to the C Runtime Library bundled with your compiler, for a variety of reasons, not the least of which is security updates that can be distributed to all versions of the operating system via updated versions of the runtime libraries. You have to have a really good reason not to use the CRT, such as if you are trying to build the world's smallest executable. And not having these functions available will only be the first of your hurdles. The CRT handles a lot of stuff for you that you don't normally even have to think about, like getting the process up and running, setting up a standard C or C++ environment, parsing the command line arguments, running static initializers, implementing constructors and destructors (if you're writing C++), supporting structured exception handling (SEH, which is used for C++ exceptions, too) and so on. I have gotten a simple C app to compile without a dependency on the CRT, but it took quite a bit of fiddling, and I certainly wouldn't recommend it for anything remotely serious. Matthew Wilson wrote an article a long time ago about Avoiding the Visual C++ Runtime Library. It is largely out of date, because it focuses on the Visual C++ 6 development environment, but a lot of the big picture stuff is still relevant. Matt Pietrek wrote an article about this in the Microsoft Journal a long while ago, too. The title was "Under the Hood: Reduce EXE and DLL Size with LIBCTINY.LIB". A copy can still be found on MSDN and, in case that becomes inaccessible during one of Microsoft's reorganizations, on the Wayback Machine. (Hat tip to IInspectable and Gertjan Brouwer for digging up the links!)

If your concern is just the need to distribute the C Runtime Library DLL(s) alongside your application, you can consider statically linking to the CRT. This embeds the code into your executable, and eliminates the requirement for the separate DLLs. Again, this bloats your executable, but does make it simpler to deploy without the need for an installer or even a ZIP file. The big caveat of this, naturally, is that you cannot benefit to incremental security updates to the CRT DLLs; you have to recompile and redistribute the application to get those fixes. For toy apps with no other dependencies, I often choose to statically link; otherwise, dynamically linking is still the recommended scenario.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • A few notes: *"The CRT handles [...] parsing the command line arguments"* - That's really the shell, that does the heavy lifting ([CommandLineToArgvW](https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391.aspx)). *"[When statically linking] you have to recompile and redistribute the application to get those fixes."* - I'd argue that you have to do the same, when dynamically linking. If you don't, you wind up distributing untested code. The Universal CRT seems to allow for that scenario, though. *"[A]ll the links seem to be dead"* - any archive.org snapshots maybe? – IInspectable Aug 16 '16 at 18:16
1

There are some C runtime functions in NtDll. According to Windows Internals these are limited to string manipulation functions. There are other equivalents such as using HeapAlloc instead of malloc, so you may get away with it depending on your requirements.

Although these functions are acknowledged by Microsoft publications and have been used for many years by the kernel programmers, they are not part of the official Windows API and you should not use of them for anything other than toy or demo programs as their presence and function may change.

You may want to read a discussion of the option for doing this for the Rust language here.

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
  • So technically it would be allright to use them? I already looked about the `HeapAlloc` instead of `malloc` but i look for the way to get rid of crt from my dll, since i use only the string functions i think i should be ok – Vlad Aug 16 '16 at 16:17
  • As far as I know, yes. – Pete Kirkham Aug 16 '16 at 16:23
  • 1
    No it would not be alright to use these undocumented functions that may not exist in all versions of ntdll, might change meaning in future versions, and won't give you any benefits anyway. What is your actual problem? You'll still be using the C runtime of your compiler anyway. All you are doing here is creating work for yourself, risk of future failure, for no benefit whatsoever. – David Heffernan Aug 16 '16 at 16:24
  • @DavidHeffernan In the context of something like a 4K demo, using something documented by WI rather than MSDN doesn't matter. In other contexts, I'd agree. – Pete Kirkham Aug 16 '16 at 16:30
  • The thing is, the asker probably thinks that this is a really good idea, for practical use. It is worth pointing out that it is an utterly bone headed idea for anything other than a silly toy program. You should at the very least point out that no amount of scraping system DLLs for helper functions gets away from the runtime support needed to get the process up and running and a `main` function executed. The C runtime of the whatever compiler is used will be needed for that. – David Heffernan Aug 16 '16 at 16:38
0

Does that mean that I can call them dynamically at runtime through LoadLibrary and GetProcAddress?

yes. even more - why not use ntdll.lib (or ntdllp.lib) for static binding to ntdll ? and after this you can direct call this functions without any GetProcAddress

Is this guaranteed to be the case for every Windows version?

from nt4 to win10 exist many C runtime functions in ntdll, but it set is different. usual it grow from version to version. but some of then less functional compare msvcrt.dll . say for example printf from ntdll not support floating point format, but in general functional is same

it is possible to drop the C runtime library altogether (by just using the CRT functions from NtDll), therefore making my program smaller?

yes, this is 100% possible.

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • 1
    *"yes, this is 100% possible"* - Well, no, not really. Writing an application that doesn't link against the CRT is nigh impossible. Cody Gray's [answer](http://stackoverflow.com/a/38980442/1889329) points out some of the lesser known things the CRT implements, that you wouldn't want to live without (SEH being a major component). Similarly, buffer security checks (`/GS` compiler switch) require the CRT. Plus, an application that doesn't link against the CRT is more readily flagged as malware by anti-malware. As always, no mention that none of this is supported or documented, hence -1. – IInspectable Aug 19 '16 at 17:00
  • @IInspectable - i write many application which work without CRT – RbMm Aug 19 '16 at 17:05
  • SEH being a major component - can use only __try/__except - implemented in ntdll buffer security checks - can off in compiler options – RbMm Aug 19 '16 at 17:07
  • 1
    Sure, you can use the compiler's `__try`/`__except` keywords, but that's only half of the SEH implementation. The remainder is buried deep in the CRT (see [A Crash Course on the Depths of Win32™ Structured Exception Handling](https://www.microsoft.com/msj/0197/exception/exception.aspx) for details). The rule is simple: No CRT, no SEH. And there's more: You won't get much floating point support (beyond basic arithmetic), and there's hardly any support for `__in64` (on x86 anyway). And of course, you won't run static initializers either, when not using the CRT. – IInspectable Aug 19 '16 at 17:19
  • @IInspectable - no this is full SEH implementation from windows view. this cannot handle c++ exception - but for low windows level this is enough. about floating point - yes, ntdll have restricted support for this(I say about printf limitation for example) - static initializers - I wrote by self `initterm()` implementation - very simply only several lines of code. so I many years write code without standard CRT – RbMm Aug 19 '16 at 17:44
  • So have you used SEH in your CRT-free applications? Now this may sound harsh, but I'll have to call you a liar, if you insist that you can use `__try`/`__except` without linking against the CRT. Stack unwinding is implemented in the CRT, for example. – IInspectable Aug 19 '16 at 17:47
  • @IInspectable - yes, I use SEH without CRT - you want got binary example ? – RbMm Aug 19 '16 at 17:49
  • @IInspectable - I say that you mistake and don't know enough without SEH – RbMm Aug 19 '16 at 17:49
  • And who's doing the stack unwinding for you then? Besides, Visual Studio won't compile (or link) your application, when you use `/NODEFAULTLIB`, and try to use SEH. Compiler support is only have the implementation (as explained in the link I posted earlier). At any rate, I do not wish to continue this. I voted on your answer, because it isn't helpful as well as dangerously undifferentiated. – IInspectable Aug 19 '16 at 17:52
  • @IInspectable - about RtlUnwind from ntdll you listen ? want you send you self binary code for can check by self ? – RbMm Aug 19 '16 at 17:57
  • in ntdllp.lib exist some static.obj for SEH support – RbMm Aug 19 '16 at 17:59
  • @IInspectable - for example sehprolog.obj - __SEH_prolog , __except_handler3, and etc.. some part implemented as static code and some (RtlUnwind) imported from ntdll – RbMm Aug 19 '16 at 18:03
  • @IInspectable - so if you want - i can at any time send you self prog with SEH (i special now incert it) with initerms and without CRT link - for prove then i not lie :) – RbMm Aug 19 '16 at 18:09
  • @IInspectable - look this part of map file - how and where SEH implemented - http://pastebin.com/SKqtKrRH – RbMm Aug 19 '16 at 18:20
  • @IInspectable - you think that i manually just wrote or linker ? still say that i liar – RbMm Aug 19 '16 at 18:23
  • It sounds like you've basically written your *own* CRT, RbMm. Which is certainly possible, the CRT is just a layer on top of functionality provided by Windows subsystems, such as kernel and the native API. What @IInspectable is saying is that the average developer is not going to be able to do this, and there is really no reason to do this for a normal application. It takes too much specialized knowledge, requires reliance on undocumented functions, and is a waste of time to implement your own CRT. Just use the one Microsoft provides, statically linking if necessary. – Cody Gray - on strike Aug 23 '16 at 04:09
  • And while I very much appreciate your answers here, because you have a very deep knowledge of Windows internals, I do share some of @IInspectable's reservations about the fact that you never mention when things are undocumented, unsupported, and implementation details. That tends to lead people astray, especially those without the same background knowledge. They aren't going to be able to go back and fix compatibility issues in the code when it breaks with the next Windows update, and they don't have any particular motivation *not* to do things the documented way. – Cody Gray - on strike Aug 23 '16 at 04:11
  • @CodyGray no - i not written my own CRT - except little code for run static initializers - http://pastebin.com/Un78GgEh - this is ALL my crt code. nothing more really not need. and about "Now this may sound harsh, but I'll have to call you a liar, if you insist that you can use __try/__except without linking against the CRT. " - yes i insist. yet one argument: – RbMm Aug 23 '16 at 06:02
  • @IInspectable are kernel drivers can (must) use __try/__except ? Yes - look `ProbeForRead` for example. are drivers can linking CRT ? No . this is absolute impossible. so how kernel drivers use __try/__except without CRT ? because they link with ntoskrnl.lib and here exist full support for SEH (as static code and import from ntoskrnl.exe). so not only msvcrt.lib (or libc.lib) support SEH but ntoskrnl.lib too. so why you can not belive that ntdllp.lib also support SEH(in __try/__except form) ?? i think IInspectable not right here. – RbMm Aug 23 '16 at 06:02
  • @CodyGray about undocumented and etc.. - not i ask this question. i simply aswer to " it is possible to drop the C runtime library" - yes this is possible and i done this more than 10+ years. I do not encourage somebody to do this. it is simply a response to the question as is – RbMm Aug 23 '16 at 06:02
  • The issue here isn't, that you are wrong. The problem with your answers (pretty much all of them) is, that you keep answering questions that ask *"Is it possible to juggle chain saws?"* with a simple, undifferentiated *"Yes, that's possible"*. No disclaimer about the dangers involved, or other prerequisites you may need to meet. Some of the *NtosKrnl.lib* exports are documented, and the exception handling involved uses `try`/`except`. There is no documentation, that the exception handling routines implemented in *ntdllp.dll* are compatible with Microsoft's C/C++ compiler. – IInspectable Aug 23 '16 at 08:57
  • @IInspectable - ok, i documentation may be not exist, but exception handling routines implemented in ntdllp.dll is full support __try/__except - how minimum i do this many years, begin from DDK 6.0 and WDK 7.1 up to lated WDK 10.0.10586.0 - and for me all work. of course i not suggest to somebody use ntdll and not deep explain how do this. but i think - if i can do this - this is possible. sometimes the exact knowledge of the possibility to do something, even without explanation how - very useful. – RbMm Aug 23 '16 at 09:42
  • *"exception handling routines implemented in ntdllp.dll is full support __try/__except"* - How could you know? Have you implemented full code coverage tests? For every Windows release, past, present, and **future**? *"of course i not suggest to somebody use ntdll"* - Please read your answer again. *"sometimes the exact knowledge of the possibility to do something, even without explanation how - very useful"* - Advice without rationale is dangerous. You never know, when good advice turns bad. This answer doesn't even have an explanation, lest so comes with a rationale. – IInspectable Aug 23 '16 at 09:56
  • @IInspectable How could you know? - i use this near 10 years and all worked in my code. from xp to win10 full code coverage tests? look - when we write __try/__except in src - c++ compiler generated some functions call (like __SEH_prolog in latest compiler or __except_handler3 - http://pastebin.com/9EexLCRC) - this is very dependent from `CL.exe` version. so this functions must be implemented somewhere. so it and implemented in `msvcrt.lib` or `libc.lib` or `ntdllp.lib` or `ntoskrnl.lib` . – RbMm Aug 23 '16 at 10:49
  • @IInspectable main problem that functions can be not found. say we have new `CL` version and old lib file. but this not only `ntdllp.lib` problem. this is common - try build code generated by latest `CL` version with old `msvcrt.lib`. but if we resolve functions link - this without doubt will work - __try/__except this is not language (like c++) but windows specific. you not sure are ntdll full compatible with `windows` ? :) again __try/__except this is not language standard. "Advice" - but this is not Advice but knowledge sharing. – RbMm Aug 23 '16 at 10:49
  • `__try`/`__except` is **not** Windows-specific. It's **compiler**-specific (and with compilers being platform-specific, it's also Windows-specific). At any rate, I'm done here. It appears to be impossible to teach you the difference between *"it appears to work"* and *"contractual guarantees"*. A cultural issue, I assume, fairly common with developers from Eastern Europe (Russia and Ukraine specifically). – IInspectable Aug 23 '16 at 10:57
  • @IInspectable `__try/__except` - here you mistaken - this is exactly Windows-specific - look for KiUserExceptionDispatcher -> RtlDispatchException (this function handle __try/__except) in ntdll. this is native exception support and RtlDispatchException of course language independed - not windows adjusted but all languages (c++ as well) MUST adjust for Windows. another thing than every languages can have self exception SHELLS over native handlers (try/except in c++ without __ ). but you ofcourse can think another :) – RbMm Aug 23 '16 at 11:06
  • `__try`/`__except` is compiler-specific. It's the compiler that generates code for setting up exception frames, or properly populated tables for table-based exception handling. None of this is done by the OS. Of course the compiler also emits code, that uses the SEH infrastructure built into the OS. But still, those keywords and their implementation are **strictly** compiler-specific. I don't know what you mean by *"try/except in c++"*. There is not `except` in C++. – IInspectable Aug 24 '16 at 15:46
  • @IInspectable - ok you're right - this really do CL jointly with CRT lib. but this CRT support exist not only in msvcrt.lib (or libc.lib), but also in ntoskrnl.lib and ntdllp.lib. yes ntdll not documented, but are you serious that ntdll crt can not conformity with `CL` ? or what you mean under " is full support __try/__except" ? i have very deep internal knowledge about how internal CL, link, windows work. and this give me very big freedom make some thing. i can go beyond the documentation – RbMm Aug 24 '16 at 19:27
  • Hey thanks for answer. I already tried that, and i also learned that i can do many fancy things just using ntdll, get rid of the import table etc. I read the Windows Internals, 6th Edition and then i looked up the disassembles for kernel32 dll, kernelbase, and ntdll. Some functions can be implemented by manipulating PEB and other interesting structures inside exe mapped in memory. – Vlad Aug 27 '16 at 00:47