这个混淆的C代码声称没有main()运行,但它真正做了什么?

Raj*_*ngh 85 c obfuscation c-preprocessor

#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)

int begin()
{
    printf("Ha HA see how it is?? ");
}
Run Code Online (Sandbox Code Playgroud)

这间接打电话main吗?怎么样?

hac*_*cks 193

C语言将执行环境定义为两类:独立式托管式.在两个执行环境中,环境都会调用函数来启动程序.
独立环境中,程序启动功能可以在托管环境中实现定义main.如果没有程序启动功能,C中的程序就无法在定义的环境中运行.

在您的情况下,main由预处理器定义隐藏.begin()将扩展到 decode(a,n,i,m,a,t,e)将进一步扩展到main.

int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main() 
Run Code Online (Sandbox Code Playgroud)

decode(s,t,u,m,p,e,d)是一个带有7个参数的参数化宏.此宏的替换列表是m##s##u##t.m, s, ut是4 ,1 ,3 和2 在替换列表中使用的参数.

s, t, u, m, p, e, d
1  2  3  4  5  6  7
Run Code Online (Sandbox Code Playgroud)

休息是没用的(只是混淆).参数传递到decode是" 一个,Ñ,,,A,T,E",所以,标识符m, s, ut与参数替换m, a, in分别.

 m --> m  
 s --> a 
 u --> i 
 t --> n
Run Code Online (Sandbox Code Playgroud)

  • 这显然是错的.在Linux上我可以使用`_start()`.或者甚至更低级别我可以尝试将我的程序的开头与引导后设置IP的地址对齐.`main()`是C Standard*library*.C本身并没有对此施加限制. (17认同)
  • @GrijeshChauhan所有的C编译器处理宏,自C89以来它是所有C标准所要求的. (11认同)
  • 你能解释一下`decode(a,n,i,m,a,t,e)`怎么变成`m ## a ## i ## n`?它会替换字符吗?你能提供一个链接到`decode`函数的文档吗?谢谢. (3认同)
  • @hacks 标准*库*确实定义了一个入口点。语言本身并不关心 (2认同)

jda*_*nay 71

尝试使用gcc -E source.c,输出以:

int main()
{
    printf("Ha HA see how it is?? ");
}
Run Code Online (Sandbox Code Playgroud)

所以main()函数实际上是由预处理器生成的.


Nli*_*tis 37

有问题的程序main()因宏观扩展而调用,但您的假设是有缺陷的 - 它根本不需要调用main()!

严格来说,你可以拥有一个C程序,并且能够在没有main符号的情况下编译它.在完成自己的初始化之后,期望跳进去的main东西c library.通常你会main从名为的libc符号跳转到_start.总是可以有一个非常有效的程序,只需执行程序集,而不需要主程序.看看这个:

/* This must be compiled with the flag -nostdlib because otherwise the
 * linker will complain about multiple definitions of the symbol _start
 * (one here and one in glibc) and a missing reference to symbol main
 * (that the libc expects to be linked against).
 */

void
_start ()
{
    /* calling the write system call, with the arguments in this order:
     * 1. the stdout file descriptor
     * 2. the buffer we want to print (Here it's just a string literal).
     * 3. the amount of bytes we want to write.
     */
    asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
    asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
Run Code Online (Sandbox Code Playgroud)

编译上面的内容gcc -nostdlib without_main.c,并Hello World!通过在内联汇编中发出系统调用(中断)来看它在屏幕上的打印.

有关此特定问题的更多信息,请查看ksplice博客

另一个有趣的问题是,您还可以拥有一个程序,该程序可以在没有main符号对应C函数的情况下进行编译.例如,您可以将以下内容作为一个非常有效的C程序,只会使编译器在您启动警告级别时发出抱怨.

/* These values are extracted from the decimal representation of the instructions
 * of a hello world program written in asm, that gdb provides.
 */
const int main[] = {
    -443987883, 440, 113408, -1922629632,
    4149, 899584, 84869120, 15544,
    266023168, 1818576901, 1461743468, 1684828783,
    -1017312735
};
Run Code Online (Sandbox Code Playgroud)

数组中的值是与屏幕上打印Hello World所需的指令相对应的字节.有关此特定程序如何工作的更详细说明,请查看此博客文章,这也是我首先阅读它的地方.

我想最后通知一下这些程序.我不知道他们是否根据C语言规范注册为有效的C程序,但是编译它们并运行它们肯定是非常可能的,即使它们违反了规范本身.


abh*_*ora 30

有人试图表现得像魔术师.他认为他可以欺骗我们.但我们都知道,程序执行开始于main().

int begin()将被替换为decode(a,n,i,m,a,t,e)由预处理器级的一个通.然后,decode(a,n,i,m,a,t,e)将替换为m ## a ## i ## n.通过宏调用的位置关联, s将具有一个字符值a.同样,u将被'i' t取代,并将被'n'取代.而且,这将是如何,m##s##u##t将成为main

关于##宏扩展中的符号,它是预处理运算符,它执行标记粘贴.扩展宏时,每个'##'运算符两侧的两个标记组合成一个标记,然后在宏扩展中替换'##'和两个原始标记.

如果您不相信我,可以使用-Eflag 编译代码.它将在预处理后停止编译过程,您可以看到令牌粘贴的结果.

gcc -E FILENAME.c
Run Code Online (Sandbox Code Playgroud)


Frx*_*rem 11

decode(a,b,c,d,[...])将前四个参数混合并加入它们以获得新的标识符dacb.(其余三个参数将被忽略.)例如,decode(a,n,i,m,[...])给出标识符main.请注意,这是begin宏定义的内容.

因此,begin宏被简单地定义为main.