最佳展示–滥用libc

2021-01-09 14:10:51

该程序由包裹在while循环中的单个print语句组成。您不会以为这会很大,但您会非常非常错。程序源的ASCII编码对正在发生的事情以及它如何工作的线索进行了编码。

如果一个玩家试图覆盖另一个玩家的举动,游戏将停止,但它会很高兴地允许一个玩家在自己之上移动。但是,鉴于这是有效的无人操作,并且可以保证输给有能力的对手,如果这样做,您可能应该输掉。

您的终端机可能每转都会对您发出蜂鸣声。我们称此功能为…该程序…很难处理您的举动!那是!请耐心等待。

宏扩展后,格式字符串大约为100K。这使一些编译器不满意,而另一些则变慢。 (例如,向GCC传递-Wall-Wextra -pedantic标志将导致有关字符串过大的警告。)

在这里,fmt是单个字符串,而arg是printf的一系列参数。

尽管它的主要目的是充当The One True Debugger,但printf也恰好是Turing的完整版。 (请参阅“控制流弯曲:关于控制流完整性的有效性”,我们在一份实际的已发表的学术论文中对此进行了介绍。有时可以避免的事情。)

我们滥用这一事实,以完全在此一个printf调用(以及对scanf()的调用以读取用户输入)内完全实现井字逻辑。

该程序使用三个printf格式说明符。-%d使用整数参数并打印它-%s使用字符串参数并打印它-%n使用指针并写入(!!)到目前为止已打印的字节数。

格式说明符可能需要额外的“参数”。-"%hhn&#34 ;:将写入mod 256的字节数存储到char指针-"%2 $ d&#34 ;:将参数2打印到printf (而不是顺序的下一个参数)-"%8d&#34 ;:将打印的整数填充为8个字符-"%3 $。* 4 $ d&#34 ;:将参数3打印到printf,使用as如参数4所示的许多零。

因为它将打印出0000000005(将5填充为大小10),然后将写入x的字节数写入。

我们使用printf将内存视为二进制数组执行任意计算-每对字节一个位:-零位由序列00 00表示-一位由序列xx 00表示,其中xx是任何非零字节。

我们可以使用格式字符串来计算任意“位”的OR / NOT。

但假设strlen(x)对于1位是1,对于0位是0,我们有

游戏本身被表示为一个18位的棋盘,每个玩家9位,以及在玩家1和玩家2之间交替的转盘计数器。

为了检测谁赢了,我们执行以下逻辑。假设A,B和C指向连续三个正方形进行测试,而D是是否存在双赢的保存位置。

"%A $ s%B $ s%C $ s%1 $ 253d%11 $ hhn" // r11 =!(* A& * B& * C)为零"%11 $ s%1 $ 255d%11hhn" // r11 =!r11ZERO"%11 $ s%D $ s%D $ hhn" // * D = * D | r11

也就是说,如果存在三行,我们将* D设置为1。对于两个玩家,我们都会针对所有可能的三合一配置重复此操作。

ZERO宏使用以下表达式确保写出的字节数为0 mod 256

其中参数1是指向临时变量的指针,后跟一个空字节。

之所以起作用,是因为如果当前计数是0 mod 256,则“%1 $ hhn”将零写入参数1,然后“%1 $ s”将永远不会发出任何文本。另一方面,如果计数不是0 mod 256,则将将长度为1的字符串写入参数1,然后“%1 $ s”将使计数增加1。通过重复此256次,我们最终将达到0 mod 256。

为了决定要打印的内容,我们必须将“内存中”的位数组强制转换为Xs和Os进行打印。这实际上很简单。给定1 $指向玩家1的平方的指针,2 $指向玩家2的平方的指针,以及3 $指向棋盘字符串的指针,我们可以计算

如果都不为真,则输出为;如果r1为true,则输出为“ X”;如果r2为true,则输出为“ O”。

为了能够最终显示面板,同时仍然仅使用oneprintf语句,我们使用

这是清除屏幕的转义序列,然后输出参数26。参数26是指向内存中char *的指针,该指针最初未定义,但是在printf语句中,我们将构造此字符串,使其看起来像atic-tac-脚趾板。

根据P1或P2的移动角度,游戏结束并有人赢了,或者游戏结束了,这是平局。

事实证明,这并不像看起来那样难。使用与前面相同的技巧,我们将字节4设置为

字节' I'和'可以始终相同,并且我们对' /' N'做相同的操作。

我们以动态方式创建scanf()格式字符串,但是出于不同的原因。我们首先要运行printf()来显示第一块木板,然后在运行之间交替进行,以scanf()和printf()读取并显示移动。重要的是,我们不希望在游戏结束时进行最后的扫描。它应该退出。

但这将使我们需要的对printf的调用次数增加一倍。 所以我们像这样实现它 (实际上,我们实际上将scanf()作为参数来避免多余声明,但是它具有相同的效果。) 请注意,现在没有初始printf()。 为了确保程序不会在第一个printf()之前阻塞,但是我们将scanf()格式初始化为nullstring,以便它立即返回而不会阻塞。 第一次运行printf()调用时,它将写出“%hhd”以创建scanf()格式字符串。 ©版权所有1984-2020,Leo Broukhis,Simon Cooper,Landon Curt Noll-保留所有权利本作品是根据Creative Commons Attribution-ShareAlike 3.0 Unported License授权的。