c - 代码是如何工作的?

标签 c obfuscation undefined-behavior

下面的代码是用 C 编写的。当给定一组输入数字时,它会跳过第一个数字,并打印其余数字。

main(i)  
{
    if(~scanf("%d",gets(&i)))
        printf("%d\n",i),main();
}

我想知道,这段代码是如何工作的?

EDIT: For those of you, who think it does not work http://www.ideone.com/cENzy

最佳答案

此代码在 C 中是不合法的。main 必须采用零个、两个或三个参数。一个参数不是合法的选项。 获取 在堆栈上涂鸦。坦率地说,如果这能奏效那就是奇迹了——未定义的行为比比皆是!

话虽如此,让我们看一下在 x86 上编译 C 代码的典型方式,以了解其工作原理。首先,main(i) 是一个旧的 K&R 风格的声明。它被解释为 int main(int i),但没有设置真正的原型(prototype) - 因此将来对 main 的调用不会对其参数进行类型检查。回想一下,在 x86 上,参数是通过在调用目标函数之前将它们压入堆栈来传递的。因此,如果我们的参数数量错误,它不会崩溃(假设您使用的是这种 ABI!),而只是提供虚假数据。

另请注意,在 x86 上,堆栈向下增长 - 当您调用函数时,当前堆栈指针减少。这意味着,如果您在这些参数之一之上破坏了内存,您将破坏属于调用函数的内存,并且在您返回之前可能不会注意到。

现在让我们看看执行流程。 gets(&i) 首先执行,并且(假设编译器忽略类型不匹配!)获取一行文本,并将它存储到覆盖调用者堆栈帧的堆栈上! 这里假设一个堆栈在内存中向下增长;在向上增长的堆栈上,这将根据字符串的长度覆盖 gets 的返回地址并可能崩溃。

尽管 gets 抓取了一行文本,但该文本将被忽略并丢弃。这是因为gets的返回值,即&i,会被传递给scanf。所以 scanf 读取一个整数并将其存储在 i 中。没问题。 scanf 然后返回 1,它是二进制否定的某个负非零值,这是真的,所以 printf 然后打印该值。然后,逗号运算符用于再次递归调用 main,并使用错误数量的参数(参数通常会使用一些虚假值进行初始化),这就像一个循环。

请注意,在 scanf 返回后,换行符保留在输入中未被使用,因此 gets 会在下一次处理它。另请注意,当 EOF 发生时,scanf 将返回 EOF (0xFFFFFFFF),逻辑取反为 0。main 将返回,并迅速崩溃,因为其调用者的堆栈已可能被 gets 覆盖。

总而言之,这是一个巧妙的 hack,但高度依赖于未定义的行为。请不要在真实代码中模仿这一点。

关于c - 代码是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5063748/

相关文章:

c - 收到错误未知类型名称 'size_t',即使我已经包含了相关的 C 库和 header

ios - 如何从 iOS 应用程序中删除这些损坏的选择器

java - 是否有一个 java 混淆器混淆了方法体但保留了方法名称?

c++ - 为什么这个简单的赋值是未定义的行为?

c++ - 具有 T=<未命名命名空间类> 的函数模板特化的静态局部变量是否必须是唯一的?

c - 使用队列类型的指针

c - 当传递给没有参数声明的函数时,参数去哪里

ios - 混淆数字(字符串) objective-c

c++ - 以下代码是否调用 UB?

c - 结构体类型数组的动态分配