Scandalous Weird Old Things About the C Preprocessor
blog.robertelder.org
blog.robertelder.org
A good knowledge of the preprocessor is essential for writing obfuscated and underhanded C. For example, the lucky7coin backdoor: https://github.com/alerj78/lucky7coin/issues/1 where the code
if (vWords[1] == CBuff && vWords[3] == ":!" && vWords[0].size() > 1)
{
CLine *buf = CRead(strstr(strLine.c_str(), vWords[4].c_str()), "r");
expands to if (vWords[1] == "PR" "IV" "M" "SG" && vWords[3] == ":!" && vWords[0].size() > 1)
{
FILE *buf = popen(strstr(strLine.c_str(), vWords[4].c_str()), "r");> So disappointing such code was not reviewed by Vern and team before running it on the server where damage could result.
So this code was actually put into production somewhere at some point -- wow. And cursory code review and compiling from source will do absolutely nothing here.
gcc -E somethinghokey.c | less
The only thing you could hope to do would be to look for PRIVMSG in the binary after compiling or to look at the file post preprocessor.
See e.g. https://lcamtuf.blogspot.com.au/2014/10/psa-dont-run-strings...
The example you gave is not really fair though, because it seems pretty obvious to me that nobody ever looked at that code - it hardly matters they hid the backdoor in the the C pre-processor. If you take a look at the repo, it only has three commits - With the first one (https://github.com/alerj78/lucky7coin/commit/07d7e5fc53e5673...) being a supposed import of the code from the repo it used to exist in, and it's in this commit where the backdoor was inserted. The real issue is that people were running code from someone who appears to be a complete unknown, has no history for his code, and just assumed it was the same as the old code without checking.
Also worth noting, one of the nicer things about the design of the C preprocessor is that it can be applied to a lot of different file-types. In more complicated low-level C projects, you can run the preprocessor over your C code, assembly code, linker scripts, etc.. which is a huge gain since you can have access to all your constants and simple macros, simplifying work and duplication. You can't get that with something tied to the language - Which is unfortunate, because like you said it's better to avoid the preprocessor, since writing things on the language-level makes them much easier to reason about.)
https://web.archive.org/web/20070209223522/http://users.foot...
Oh, I have the sources. I haven't lost anything. Why I don't host that project anywhere is that I'm not all that proud of it.
I worked for one startup some almost years ago whose guys looked at that thing before they hired me and liked it.
MPP has namespaces, and it also tries to preserve whitespace (expansions occurring at some indentation level are indented). It could be used for Python, in theory.
Thinking along those lines, I posted to a Python newsgroup around then: reactions were mixed:
http://code.activestate.com/lists/python-list/9193/
:)
http://reviews.llvm.org/D15866
#define FOO
#define BAR defined(FOO)
#if BAR
...
#else
...
#endif
clang and gcc will pick the #if branch while Visual Studio will take the #else branch.I am also reminded of the button I used to have that said, "Defining define is undefined."
https://gcc.gnu.org/onlinedocs/cpp/Computed-Includes.html
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_HIt might be telling that I also don't understand the Clang bug report as written. I think there are typos in the examples. Is the switch from "HAVE_FOO_BAR" to "HAVE_FOO" in the first example intentional? Is the construct "#defined" (with a final 'd') intentional in the second?
If it helps any here is the real-world code where this problem came up:
https://codereview.chromium.org/1584203002/
Edit: another reference: https://gcc.gnu.org/onlinedocs/cpp/Defined.html#Defined "If the defined operator appears as a result of a macro expansion, the C standard says the behavior is undefined."
#define FOO
#define BAR defined(FOO)
#if BAR
#error "true"
#else
#error "false"
#endif
Clang, GCC, and ICC evaluate the "true" branch, while MSVC evaluates the "false" branch.For the same test code with first line changed to "undef":
#undef FOO
#define BAR defined(FOO)
#if BAR
#error "true"
#else
#error "false"
#endif
MSVC, Clang, GCC, and ICC all agree on "false".Importantly, though, when used with "/Wall", MSVC gives this error message in both cases:
main.cpp(3): warning C4668: 'definedFOO' is not defined as
a preprocessor macro, replacing with '0' for '#if/#elif'
None of the other three compilers give any warnings even with '-Wall -Wextra -pedantic". So there definitely is a difference in behavior, but I don't think it's actually the one that's presumed in that bug.For further experimentation, Clang, GCC, and ICC can be tested online here: http://gcc.godbolt.org/
And MSVC can be tested online here: http://webcompiler.cloudapp.net/
#define a(x,y) x+y
void f(int i)
{
a(i,+);
}
will compile without errors with cl but not with any other compiler.Same with the bit about concatenating tokens. Every single one of those examples has a static parse tree, which, for the C preprocessor, is a sequence of tokens and directives. The author seems to be confusing the preprocessor's parse tree with the effect it has on the underlying text.
(Yes, the output of the preprocessor is dependent on what you define, but that has nothing to do with the grammar. What the author claims is like saying a Lisp is context-sensitive because the factorial function produces a different values for different inputs!)
Now, if you could do this:
#define foobar define
#foobar x 123
x
and get "123", that would be a context-sensitive grammar. But that is NOT a thing you can do!http://conal.net/blog/posts/the-c-language-is-purely-functio...
I don't recall all the horrid details, but one case that I do remember driving me nuts was the use of #if/#endif in the argument to a function-like macro.
Macros should always be the absolute last resort to doing anything. Stepping through code in gdb with some "creative" macro-based API is almost as bad as C++.
More modern compilers allow using non-static "const" constructs to do much of the same, which is a great improvement.
To wit:
#define in_bounds(lower,x,upper) ((x <upper) && (x >lower))
vs. const bool in_bounds = ((x > lower) && ( x < upper ));
Macros can be used effectively for reduction in strength, so long as you're not too clever about it.
You don't have to keep the preprocessing of files as part of the mainline build, but there's something to be said for it - sort of "make GENERATE_ALL_THE_THINGS" might run the preprocessing { Python/Tcl/Perl/bash/even 'C' } scripts for you.
If the generators just emit .h files, that can be pretty good. You're still left with something #ifdef-ey to select them, based on #defines or -D options.
You might even go so far as to dynamically load these tables if that can make sense. The ld linker can directly link in blobs.