Rau*_*akS 17 c argument-passing deobfuscation
在大学编程竞赛中提出了以下问题.我们被要求猜测输出和/或解释它的工作原理.不用说,我们都没有成功.
main(_){write(read(0,&_,1)&&main());}
Run Code Online (Sandbox Code Playgroud)
一些简短的谷歌搜索引导我到这个确切的问题,问codegolf.stackexchange.com
:
https://codegolf.stackexchange.com/a/1336/4085
在那里,它解释了它的作用:Reverse stdin and place on stdout
但不是如何.
我也在这个问题上找到了一些帮助:主要的三个论点,以及其他混淆的技巧,
但它仍然没有解释如何main(_)
,&_
并且&&main()
有效.
我的问题是,这些语法如何工作?它们是我应该知道的,因为它们仍然相关吗?
如果不是直接的答案,我会感激任何指针(资源链接等).
Rob*_*obᵩ 26
这个程序做什么用的?
main(_){write(read(0,&_,1)&&main());}
Run Code Online (Sandbox Code Playgroud)
在我们分析它之前,让我们美化它:
main(_) {
write ( read(0, &_, 1) && main() );
}
Run Code Online (Sandbox Code Playgroud)
首先,您应该知道这_
是一个有效的变量名称,虽然是一个丑陋的名称.让我们改变它:
main(argc) {
write( read(0, &argc, 1) && main() );
}
Run Code Online (Sandbox Code Playgroud)
接下来,实现函数的返回类型和参数的类型在C中是可选的(但不是在C++中):
int main(int argc) {
write( read(0, &argc, 1) && main() );
}
Run Code Online (Sandbox Code Playgroud)
接下来,了解返回值的工作原理.对于某些CPU类型,返回值始终存储在相同的寄存器中(例如,x86上的EAX).因此,如果省略return
语句,则返回值可能是最新函数返回的值.
int main(int argc) {
int result = write( read(0, &argc, 1) && main() );
return result;
}
Run Code Online (Sandbox Code Playgroud)
调用read
或多或少是明显的:它从标准的(文件描述符0)读取到位于&argc
的1
字节的内存中.1
如果读取成功则返回,否则返回0.
&&
是逻辑"和"运算符.当且仅当它的左侧是"真"时(技术上,任何非零值),它评估其右侧.&&
表达式的结果int
是始终为1(对于"true")或0(对于false).
在这种情况下,右侧main
不带参数调用.调用main
1个参数声明之后不带参数是不确定的行为.然而,它通常有效,只要您不关心argc
参数的初始值.
然后将结果&&
传递给write()
.所以,我们的代码现在看起来像:
int main(int argc) {
int read_result = read(0, &argc, 1) && main();
int result = write(read_result);
return result;
}
Run Code Online (Sandbox Code Playgroud)
嗯.快速查看手册页显示write
需要三个参数,而不是一个.另一种未定义行为的情况.就像main
用太少的参数调用一样,我们无法预测write
第二和第三个参数会得到什么.在典型的计算机上,他们会得到一些东西,但我们无法确定是什么.(在非典型计算机上,可能会发生奇怪的事情.)作者依赖于write
接收先前存储在内存堆栈中的任何内容.而且,他是在依靠的是作为第二和第三个参数的读取.
int main(int argc) {
int read_result = read(0, &argc, 1) && main();
int result = write(read_result, &argc, 1);
return result;
}
Run Code Online (Sandbox Code Playgroud)
修复无效调用main
,添加标题,扩展&&
我们:
#include <unistd.h>
int main(int argc, int argv) {
int result;
result = read(0, &argc, 1);
if(result) result = main(argc, argv);
result = write(result, &argc, 1);
return result;
}
Run Code Online (Sandbox Code Playgroud)
该程序在许多计算机上无法正常工作.即使您使用与原始作者相同的计算机,它也可能无法在其他操作系统上运行.即使您使用相同的计算机和相同的操作系统,它也无法在许多编译器上运行.即使您使用相同的计算机编译器和操作系统,如果更改编译器的命令行标志,它也可能不起作用.
正如我在评论中所说,问题没有一个有效的答案.如果您发现比赛组织者或比赛裁判员另有说明,请不要邀请他们参加您的下一场比赛.
好的,_
它只是在早期K&R C语法中声明的变量,其默认类型为int.它起临时存储的作用.
程序将尝试从标准输入读取一个字节.如果有输入,它将以递归方式调用main继续读取一个字节.
在输入结束时,read(2)
将返回0,表达式将返回0,write(2)
系统调用将执行,并且调用链可能会展开.
我在这里说"可能",因为从这一点来看,结果是高度依赖于实现的.write(2)
缺少其他参数,但有些东西将在寄存器和堆栈中,因此会将某些内容传递给内核.相同的未定义行为适用于各种递归激活的返回值main
.
在我的x86_64 Mac上,程序读取标准输入直到EOF然后退出,根本不写任何内容.