# TSG CTF 2020 writeup by NaruseJun ## Reverse-ing by amama I ran the `reverse` program and saw input nothing, so I hit some keys then the program suddenly finished with the word 'wrong'. I guessed the program is getting a line, and checking the line is the same as the flag, then print 'correct' or 'wrong' (all of these 'rev' problems the kind like this, isn't it?). Next, to debugging the `reverse` program, check symbols in the program using `objdump` command (I know, usually we use `readelf` instead of `objdump` in general, but in my case, I just want to know the entry point of the program). `objdump -d reverse` ``` 00000000006000b0 <reverse>: 6000b0: 9c pushfq 6000b1: 57 push %rdi 6000b2: 56 push %rsi 6000b3: 52 push %rdx 6000b4: 51 push %rcx 6000b5: 53 push %rbx ...(snip)... 00000000006000e5 <_start>: 6000e5: 48 31 db xor %rbx,%rbx 6000e8: bb b0 00 60 00 mov $0x6000b0,%ebx 6000ed: ff d3 callq *%rbx 6000ef: c1 fb 61 sar $0x61,%ebx 6000f2: 5e pop %rsi 6000f3: 19 b2 25 ff d3 8c sbb %esi,-0x732c00db(%rdx) 6000f9: 19 2b sbb %ebp,(%rbx) 6000fb: 63 72 48 movslq 0x48(%rdx),%esi ...(snip)... ``` Now we can get `reverse` function and `_start` functions, and the latter is the entry point. It is time to debugging! I prepared to run the `reverse` program again with `gdb`, and set breakpoints to the `_start` function and the `reverse` function, then ran `reverse` and the program stoped in entering `_start` function. Using stepwise instruction execution (command `si`), you can see the mechanism of the program. Surprisingly, the program repeat executing an instruction and calling the `reverse` function again and again! How does the `reverse` function work? Let's disassemble the `reverse` function. The core of the `reverse` function is below. Execute these instructions in your mind or gdb, now you can understand these instructions literally 'reverse' the code. So, the next instruction after the `reverse` function executed is the bottom binary of the code area. Recalling the `reverse` function's code and continue to debug, you realize that important instructions are the instruction after the `reverse` function. The core part of the `_start` function does 1. read the input (using syscall) 2. compare the input and the flag (of course, the flag is encrypted) 3. output the suitable string ('correct' or 'wrong') Parts 1 and 3 are not important to solve the problem, so we pay attention to part 2. Part 2 decrypts flag and compares to the input, therefore we simulate the sequence of decryption and get the decrypted flag. The detail of part 2 is a simple arithmetic operation. let input[37] be an input of the program (the number, 37, is easily seen in the resister when syscall instruction is executed). We can see the suquence like the Pseudo code. Pseudo code is: ``` unsigned char *array1, *array2; for(i = 36; i; i--) { if((input[i] ^ array1[i]) + array2[i] != 0) return print("wrong"); } print("correct"); ``` Hence, using array1 and array2 and apply inverse projection of the operation, now we get the flag. Dumping array1 and array2 using dgb (the addresses of array1 and array2 in the run-time are both `0x600194` but inside of array is actually different because `reverse` oparation reverses around these addresses), get these values. ``` array1 = { -28,-45,-1, 5, 15, 107,124, 19, -1, -54,-45,-1, -1, 49, 72, 114, 99, 43, 25, -116, -45,-1, 37, -78, 25, 94, 97, -5, -63,-45,-1, 0, 96, 0, -80,-69,-37}; array2 = { 72, -128, 70, -70,-91,-45,-1, -64, 49, 72, 30, 101,50, -92,-120, -45, -1, -26,-119, 72, 95, 122,-124, 59, -45,-1, -46,49, 72, 78, 54, -55, -59,-49,34, 50, 88}; ``` decrypt code is here: ```c int adder[] = { -28,-45,-1, 5, 15, 107,124, 19, -1, -54,-45,-1, -1, 49, 72, 114, 99, 43, 25, -116, -45,-1, 37, -78, 25, 94, 97, -5, -63,-45,-1, 0, 96, 0, -80,-69,-37}; int xorer[] = { 72, -128, 70, -70,-91,-45,-1, -64, 49, 72, 30, 101,50, -92,-120, -45, -1, -26,-119, 72, 95, 122,-124, 59, -45,-1, -46,49, 72, 78, 54, -55, -59,-49,34, 50, 88}; main() { for(int i = 0; i < 37; i++) { for(int c = 0; c < 128; c++) { if(((c ^ (i%2?adder:xorer)[i]) + (i%2?xorer:adder)[i]) == 0) printf("%c", c); } } } ``` Note: It is important to swap `adder` and `xorer` because these instruction's number is odd, `reverse` function reverses the code one by one. #### appendix ``` 0x6000ef <_start+10>: xor %rdx,%rdx 0x6000f4 <_start+15>: mov $0x25,%dl 0x6000f8 <_start+19>: mov %rsp,%rsi 0x6000fd <_start+24>: xor %rdi,%rdi 0x600102 <_start+29>: xor %rax,%rax 0x600107 <_start+34>: syscall 0x600110 <_start+43>: xor %rcx,%rcx 0x600115 <_start+48>: mov (%rsi,%rdx,1),%cl 0x60011a <_start+53>: xor 0x600194(%rdx),%cl // 88 0x600122 <_start+61>: add 0x600194(%rdx),%cl // -37 0x60012a <_start+69>: or %rcx,%rdi 0x60012f <_start+74>: dec %dl 0x600133 <_start+78>: jns 0x600171 <_start+140> 0x600176 <_start+145>: xor 0x600194(%rdx),%cl // -69 0x60017e <_start+153>: add 0x600194(%rdx),%cl // 50 ...(snip)... ``` Actually `call reverse` is injected between every instruction. ## Self Host by amama **If you cant read Japanese, use DeepL or Google Translation** TL;DR Quine書いた。終わり ### 序文 プログラマが行う知的な遊びの一つとして、自分自身を出力とするプログラムを書く、というものがある。 これは[Quine](https://en.wikipedia.org/wiki/Quine_(computing))と呼ばれており、インターネットで検索するといろいろなQuineのソースコードを見ることができるだろう。 では、Quineは一体どうやって書けばいいのだろうか。 ここでは、一般的なプログラミング言語に使える技術を紹介するとともに、それをコンパイラに応用する方法を示す。 ### Write Quine さて、実際にQuineを書くにあたって、ここで目指すQuineの一般形をここに示しておこう。 これはC言語風の疑似ソースコードだが、Quineの本質部分は十分理解できるはずだ。 ``` decode(s) { for(c in s) { switch(c) { case 64: // 64 is at-mark (@) putc(34); // 34 is double-quatation (") print(s); putc(34); default: putchar(c); } } } decode("decode(s) {for(c in s) { switch(c) {case 64: putchar(34);print(s); putc(34); default: putc(c); }}} decode(@);"); ``` では、このソースコードに到達するための道のりを説明する。 まずプログラマ諸氏がナイーブに「自分自身を出力するプログラム」を書こうと思ったとき、上記のようなプログラムを書こうと思う人は居ないはずだ。 以下のようなプログラムを書こうとして、[無限後退](https://en.wikipedia.org/wiki/Infinite_regress)に陥っていることに気づくだろう。 ``` // first approarch s = "print(s);"; print(s); → print(s); // second approach s = "s = \"print(s)\"; print(s);"; print(s); → s = "print(s)"; print(s); // third approach s = "s = \"s = \\\"print(s)\\\"; print(s)\"; print(s)"; print(s); → s = "s = \"print(s)\"; print(s);"; print(s); // and so on ... ``` 当然、上記のアプローチはうまく行かない。 見てもらえば分かる通り、このプログラムの問題点は`s`に「自分自身」の文字列を格納しようとするが、実際の出力とソースコードがどうやっても一致しないため、どんどん文字列が長くなってしまうことにある。 この問題点を解決するアプローチの一つとして、文字列の中に「自分自身」を表す文字を入れ、その文字が来たら自分自身を出力する、という方法がある。 ``` decode(s) { for(c in s) { switch(c) { case 64: // 64 is at-mark (@) print(s); default: putchar(c); } } } decode("decode(s) {for(c in s) { switch(c) {case 64:print(s); default: putchar(c); }}} decode(\"@\");"); → decode(s) {for(c in s) { switch(c) {case 64:print(s); default: putchar(c); }}} decode("decode() {for(c in s) { switch(c) {case 64:print(s); default: putchar(c); }}} decode("@");"); ``` この`decode`関数による出力は自分自身と同じ文字列を出力するように見える。 しかし、注意深い読者なら直ちに気がつくであろうが、実は2箇所違う場所がある。 このバージョンは`decode`関数の引数の文字列に「`"`」を含むため、当該箇所(`…… decode(\"@\");");`)を「`\"`」としているが、「`\"`」のプログラム中における値は「`"`」であるため、出力された文字列をよく見ると、`…… decode("@");");` となってしまっている。 この問題を解決するため、`decode`関数を改良する。 最もナイーブな解決方法としては、 ``` case 64: // 64 is at-mark (@) putc('"'); print(s); putc('"'); ``` とすることであるが、この方法はうまくいかない。 なぜなら、肝心の`decode`関数に渡す文字列の中に「"」を出現させない、という目標が達成できていないからだ。 たしかに、`…… decode(\"@\");");`は`…… decode(@);");`と書けるようになったが、今度は関数の中身を文字列化するときに`case 64:print(s);`が`case 64:putc('\"'); print(s);putc('\"');` となってしまう。 このように、ソースコード中に「`"`」を出現させずにコードを書く必要があるが、その値に対する複数の表現方法があれば問題点を回避できるような表現方法を選択することによって解決できる。 たとえば、大抵のプログラミング言語ではある文字とそれを表現するASCII/unicodeは互いに変換可能なことが多いので、これを利用して「`"`」のASCIIコードである34を代替表現として用いることで`decode`関数が完成する。 Quineを書くときには、「文字列中に存在してはいけない文字」が出てこないように注意深くコードを書くことが必要である。 つまり、Quineを書くためには、 - 値をデコードして出力するプログラム - 「値をデコードして出力するプログラム」をエンコードした値 の2つがあればよいということがわかる。 ### [Reflections on Trusting Trust](https://users.ece.cmu.edu/~ganger/712.fall02/papers/p761-thompson.pdf) 次に、Quineを応用してコンパイラに悪意のあるコードを侵入させる方法について示す。 この問題の目標は「「「攻撃者のコンパイラでコンパイルしたcompiler.x」でコンパイルしたcompiler.x」でflagの1文字目を出力するコードをコンパイルしたとき、すべての文字列を出力するプログラムを出力する」ということである。 #### そもそもなぜ3回コンパイルするのか コンパイラを使用してプログラムをコンパイルするとき、そのコンパイラにバグがあったら成果物が正しく動かない。そこで、コンパイラにバグが有るかどうかを判断したい、という問題がある。 この問題を解決するため、[3-stage bootstrap](https://en.wikipedia.org/wiki/Bootstrapping_(compilers))という手法が使用される。 まず、言語Xで書かれた言語Xのコンパイラのソースコード $C^0_s$ にバグがあるかもしれない、と仮定する。 そして、$C^0_s$ を既存のコンパイラ $C_{\text{native}}$ でコンパイルして成果物であるコンパイラ $C^0$ を得る。 しかし、$C^0_s$ にはバグがあるかもしれないので $C^0$ は正しく動かないかもしれない。 すなわち、$C^0$ でコンパイルした成果物が正しく動かないかもしれない、ということである。 このコンパイラで自分自身 $C^0_s$ をコンパイルすると、「正しく動かないかもしれないコンパイラでコンパイルされたコンパイラ $C^1$」が手に入る。 もし $C^0_s$ に - バグが**なければ** $C^0$ は正しく**動く**ので、結果は「正しく動くコンパイラでコンパイルされた正しく動くコンパイラ $C^1_{\text{correct}}$ 」になる。 - バグが**あれば** $C^0$ は正しく**動かない**ので、結果は「正しく動かないコンパイラでコンパイルされた正しく動かないコンパイラ $C^1_{\text{incorrect}}$ 」になる。 さて、このどちらかのコンパイラが生成されるわけだが、当然、正しく動くコンパイラを使用したい。 では、どうやったら見分けることができるのだろうか? 例えば、これによって得られた $C^1$ で同じソースコード $C^0_s$ をコンパイルするとどうなるか考えてみよう。 結果は、$C^1_{\text{correct}}$ でコンパイルされたコンパイラ $C^2_{\text{correct}}$ か、$C^1_{\text{incorrect}}$でコンパイルされたコンパイラ $C^2_{\text{incorrect}}$ となる。 もし $C^0_s$ が正しく動いていれば、 $C^1$ と $C^2$ を比較すると一致するはずである。 そして、この対偶を取ると、「もし$C^1$ と $C^2$ を比較して一致しなければ、$C^0_s$ は正しく動いていない」ということがわかるので、$C^1$ と $C^2$ が一致するならば(成果物の生成に対しては) 正しく振る舞うようなコンパイラ$C^0_s$が書けた、と言える。 ちなみに $C^0$ と $C^1$ を比較しても、コンパイルしたコンパイラのソースが違うため($C^0$ を出力したのは$C_{\text{native}}$で $C^1$ を出力したのは $C^0_s$ からできたコンパイラである)、意味がないことに注意してほしい。 #### inject evil code CTFに参加する諸君はインターネットからソフトウェアやライブラリの実行可能バイナリをダウンロードして利用することはせず、対象となるOSSのソースコードを読み込み悪意のあるコードが混入していないか検査してからコンパイルして利用することが一般的であると思うが、そのコンパイラに悪意がないとどうして言い切れるのだろうか? コンパイラにバグがあるかを判断する手法として3回コンパイルするという手法があることを説明したが、悪意のあるコードが挿入されているかどうかを判断することはこの方法では不可能であることを同様の論法を持って紹介する。 まず、言語Xのコンパイラ $C'_{\text{native}}$ に**悪意のあるコード**があるかもしれない、と仮定する。 そして、事前の検証によって安全であることがわかっている「言語Xで書かれた言語Xのコンパイラのソースコード $C'^0_s$」を$C'_{\text{native}}$ でコンパイルして成果物であるコンパイラ $C'^0$ を得る。 しかし、$C'_{\text{native}}$ には信用できないので $C'^0$ も信用できない。 すなわち、$C'^0$ でコンパイルした成果物に悪意のあるコードが混入されているかもしれない、ということである(例えば、成果物に対してある種のバックドアを仕込むようなものかもしれない)。 このコンパイラで自分自身 $C'^0_s$ をコンパイルすると、「信用できないコンパイラでコンパイルされたコンパイラ $C'^1$」が手に入る。 もし $C'_{\text{native}}$ に - 悪意のあるコードが**なければ** $C'^0$ は正しく動くので、結果は「正しく動くコンパイラでコンパイルされた正しく動くコンパイラ $C'^1_{\text{correct}}$ 」になる。 - 悪意のあるコードが**あれば** $C'^0$ は正しく動かないので、結果は「正しく動かないコンパイラでコンパイルされた正しく動かないコンパイラ $C'^1_{\text{evil}}$ 」になる。 さて、このどちらかのコンパイラが生成されるわけだが、信用できるかどうかを見分けられるだろうか? 同じように、これによって得られた $C'^1$ で同じソースコード $C'^0_s$ をコンパイルするとどうなるか考えてみよう。 結果は、$C'^1_{\text{correct}}$ でコンパイルされたコンパイラ $C'^2_{\text{correct}}$ か、$C'^1_{\text{evil}}$でコンパイルされたコンパイラ $C'^2_{\text{evil}}$ となる。 バグの有無を判断するときには、「もし$C^1$ と $C^2$ を比較して一致しなければ、$C^0_s$ は正しく動いていない」ということを判断基準にしたが、この場合、悪意のあるコードが「『入力 $C'^0_s$ に対しては $C'_{\text{native}}$ のように振る舞う』というコードを挿入する」のようなものであった場合、$C'^1$ に「入力 $C'^0_s$ に対しては $C'_{\text{native}}$ のように振る舞う」 というコードが挿入されてしまうため、$C'^1$ と $C'^2$ は常に一致してしまう。 よって、この方法ではコンパイラに悪意があるかどうかを判断することができない。 では、どうやってこの悪意のあるコード挿入を達成すればよいのだろうか。 一般にコンパイラとは「入力として言語Xのソースコードを受け取り、言語Yのソースコードを出力するプログラム」とみなすことが多いが、ここでは更に一般化して、「入力xを受け取り、yを出力するプログラム」であると解釈する。 ここで、「言語Xで書かれた『言語Yを受け取り言語Zを出力するプログラム』」を $Y \xrightarrow[X]{} Z$ と表すことにする。 $X \xrightarrow[X]{} Y$を$X \xrightarrow[x]{} Y$に入力として与えると、出力として $X \xrightarrow[Y]{} Y$が手に入るが、もし $x = Y$ であればこれはある入力($X \xrightarrow[X]{} Y$)に対してQuine的に振る舞う、ということがわかる(すなわち、出力と自分自身が一致しているということである)。 さて、単純なQuineが書けることはすでにわかっているが、このような「ある入力に対してQuine的に振る舞うプログラム」に対してこの技術を応用したい。 この問題の目標は「「「攻撃者のコンパイラでコンパイルしたcompiler.x」でコンパイルしたcompiler.x」でflagの1文字目を出力するコードをコンパイルしたとき、flagのすべての文字列を出力するプログラムを出力する」であるので、 - `code` が 攻撃者のコンパイラ - `code2` が「攻撃者のコンパイラでコンパイルしたcompiler.x」 - `code3` が「攻撃者のコンパイラでコンパイルしたcompiler.x」でコンパイルしたcompiler.x である。 また、 - $F$ を flagの1文字目を出力するコード - $F_{\text{evil}}$ を flagのすべての文字列を出力するプログラム とする。 つまり、`code3` で $F$ をコンパイルしたとき、 $F_{\text{evil}}$ を出力せよ。 という問題である。 よって、`code3` は、 - 「入力が $F$ の場合、$F_{\text{evil}}$ を出力する」という悪意のあるコードが挿入されたコンパイラであり、 - このコンパイラを出力する `code2` は、 - 「入力が `compiler.x` の場合、『$F_{\text{evil}}$ を出力する』というコードを挿入する」という悪意のあるコードが挿入されたコンパイラであり、 - このコンパイラを出力する `code` は、 - 「入力が `compiler.x` の場合、『入力が `compiler.x` の場合、【$F_{\text{evil}}$ を出力する】というコードを挿入する』というコードを挿入する」コンパイラである。 そして、problem.pyには `code2 == code3` を確認するチェックが61行目にかかれているため、`code2` は本質的に`code3`と同じことができなければならず、逆もまた然りである。 よって、`code3` は - 「入力が $F$ の場合、$F_{\text{evil}}$ を出力する」という悪意のあるコードが挿入されたコンパイラであり、かつ、「入力が `compiler.x` の場合、『$F_{\text{evil}}$ を出力する』というコードを挿入する」という悪意のあるコードが挿入されたコンパイラである そして、`code3` を出力する `code2` は - 「入力が $F$ の場合、$F_{\text{evil}}$ を出力する」という悪意のあるコードが挿入されたコンパイラであり、かつ、「入力が `compiler.x` の場合、『$F_{\text{evil}}$ を出力する』というコードを挿入する」という悪意のあるコードが挿入されたコンパイラである ので、`code2` は `compiler.x` を入力としたとき、Quine的に振る舞うことがわかる。 以下、`code2` のソースコードを `compiler2.x` として説明する。 `compiler2.x` は、 - 「入力が `compiler.x` ならば、入力を `compiler2.x` とみなす」というコードになっている - 「入力が $F$ の場合、$F_{\text{evil}}$ を出力する」というコードを挿入するコードになっている 上の条件はまさしくQuineと同様の手法を使ってコードを書くことができる。 また、ここでは簡単のため、上記の機能を実装するときに入力をすべてチェックすることはせず、flagが入手できる程度に`compiler2.x`が書ければ良いものとする。 「入力が `compiler.x` ならば」という部分は `s = tokenize()`が終わったあとに、`main` や `tokenize` などといった識別子が `s` の中に存在しているかで判断すればよいだろう。 さて、「入力を `compiler2.x` とみなす」という部分はどのように実装すればよいのだろうか。 `compiler2.x`のコードをそのまま文字列にすると無限後退が起きてしまうということは学習したので、Quineと同様に - 入力を `compiler2.x` とみなすプログラム - 「入力を `compiler2.x` とみなすプログラム」をエンコードした値 を実装すればよい。 「入力を `compiler2.x` とみなすプログラム」を実装したものが以下である。また、コード中に出てくる `qlist` は「入力を `compiler2.x` とみなすプログラム」をエンコードした値が入っている。 `inj_ret = inj_ret + escape ( qlist ) ;` の部分が、自分自身を出力する行為に相当する。 ``` qlist ; escape ( q ) { e_i = 0 ; e_ret = [ ] ; while ( e_i + 1 < len ( q ) ) { e_ret = e_ret + [ [ 34 ] + q [ e_i ] + [ 34 ] ] ; e_ret = e_ret + [ [ 44 ] ] ; e_i = e_i + 1 ; } e_ret = e_ret + [ [ 34 ] + q [ e_i ] + [ 34 ] ] ; return e_ret ; } injection ( s ) { inj_i = 0 ; inj_ret = [ ] ; while ( inj_i < len ( s ) ) { if ( inj_i + 9 < len ( s ) ) { if ( s [ inj_i ] == "main" && s [ inj_i + 1 ] == "(" && s [ inj_i + 2 ] == ")" && s [ inj_i + 3 ] == "{" && s [ inj_i + 4 ] == "s" && s [ inj_i + 5 ] == "=" && s [ inj_i + 6 ] == "tokenize" && s [ inj_i + 7 ] == "(" && s [ inj_i + 8 ] == ")" && s [ inj_i + 9 ] == ";" ) { inj_ret = inj_ret + qlist ; inj_ret = inj_ret + [ "main" , "(" , ")" , "{" , "s" , "=" , "tokenize" , "(" , ")" , ";" ] ; inj_ret = inj_ret + [ "qlist" , "=" , "[" ] ; inj_ret = inj_ret + escape ( qlist ) ; inj_ret = inj_ret + [ "]" , ";" ] ; inj_ret = inj_ret + [ "s" , "=" , "injection" , "(" , "s" , ")" , ";" ] ; inj_i = inj_i + 10 ; } else { inj_ret = inj_ret + [ s [ inj_i ] ] ; inj_i = inj_i + 1 ; } } else { inj_ret = inj_ret + [ s [ inj_i ] ] ; inj_i = inj_i + 1 ; } } return inj_ret ; } main ( ) { s = tokenize ( ) ; qlist = [ "qlist" , ";" , "escape" ... ] ; s = injection ( s ) ; ... } ``` このコードをエンコードして即値として `qlist` に代入すれば、「入力が `compiler.x` ならば、入力を `compiler2.x` とみなす」というコードができる。 実際は、同時に「入力が $F$ の場合、$F_{\text{evil}}$ を出力する」というコードを挿入するコードにでもなければならないので、以下のコードを `injection` 関数に追記する。 ``` inj_ret = inj_ret + [ "if" , "(" , "s" , "[" , "0" , "]" , "==" , ( [ 34 ] + "flag" + [ 34 ] ) , "&&" , "s" , "[" , "1" , "]" , "==" , ( [ 34 ] + ";" + [ 34 ] ) , "&&" , "s" , "[" , "2" , "]" , "==" , ( [ 34 ] + "main" + [ 34 ] ) , "&&" , "s" , "[" , "3" , "]" , "==" , ( [ 34 ] + "(" + [ 34 ] ) , "&&" , "s" , "[" , "4" , "]" , "==" , ( [ 34 ] + ")" + [ 34 ] ) , "&&" , "s" , "[" , "5" , "]" , "==" , ( [ 34 ] + "{" + [ 34 ] ) , "&&" , "s" , "[" , "6" , "]" , "==" , ( [ 34 ] + "flag" + [ 34 ] ) , "&&" , "s" , "[" , "7" , "]" , "==" , ( [ 34 ] + "=" + [ 34 ] ) , ")" , "{" , "s" , "=" , "[" , ( [ 34 ] + "flag" + [ 34 ] ) , "," , ( [ 34 ] + ";" + [ 34 ]) , "," , ( [ 34 ] + "main" + [ 34 ] ) , "," , ( [ 34 ] + "(" + [ 34 ] ) , "," , ( [ 34 ] + ")" + [ 34 ] ) , "," , ( [ 34 ] + "{" + [ 34 ] ) , "," , ( [ 34 ] + "flag" + [ 34 ] ) , "," , ( [ 34 ] + "=" + [ 34 ] ) , "," , "s" , "[" , "8" , "]" , "," , ( [ 34 ] + ";" + [ 34 ] ) , "," , ( [ 34 ] + "write" + [ 34 ] ) , "," , ( [ 34 ] + "(" + [ 34 ] ) , "," , ( [ 34 ] + "flag" + [ 34 ] ) , "," , ( [ 34 ] + ")" + [ 34 ] ) , "," , ( [ 34 ] + ";" + [ 34 ] ) , "," , ( [ 34 ] + "}" + [ 34 ] ) , "]" , ";" , "}" ] ; ``` これは、「入力が $F$ の場合、$F_{\text{evil}}$ を出力する」というコードをエンコードしたものになっている。 また、これらのコードはQuineを書くときに注意しなければならない「文字列中に存在してはいけない文字」である「`"`」が出現してしまっているので、エンコードするときに `"main"` などという文字列は代替表現である `[109, 97, 105, 110]` になおしてからエンコードする。 例えば、`inj_ret = inj_ret + [ "]" , ";" ] ;` というコードをエンコードするときには、`"`が入っていない等価なコードである`inj_ret = inj_ret + [ [93], [59] ] ;`とみなしエンコードする。 エンコード後は `[[101, 110, 106, 95, 114, 101, 116], [61], [101, 110, 106, 95, 114, 101, 116], [43], [91], [91], [57, 51], [93], [44], [91], [53, 57], [93], [93], [59]]` というリストとなり、これは `[ "inj_ret", "=", "inj_ret", "+", "[", "[", "93", "]", ",", "[", "59", "]", "]", ";"]` という表現と等価である。 エンコードの際に用いたスクリプトが以下である。 このスクリプトは、空白区切りでtokenizeされた文字列を受け取り、エンコードした値を返す。 エンコードした値は、文字列のリストであるので、数値のリストのリストが出力される。 ``` a = input().split(' ') lis = [] for s in a: e = [] if len(s) == 0: continue for c in s: e.append(ord(c)) t = ', '.join(map(str, e)) lis.append('[' + t + ']') print('[' + ', '.join(lis) + ']') ```