2>/dev/null

ふぉれんじっくやさんになりたいです

記号のみPowerう〇こshell芸入門

Aizu Advent Calendar 2019の13日目の記事です。

また、9日遅れでの投稿でもあります。誠に申し訳ございません…。

 

adventar.org

 

序論

ところで、皆さんはシェル芸人と呼ばれるものをご存知でしょうか。

シェル芸人とは、USP友の会様によると

シェル芸 とは、主にUNIXオペレーティングシステムにおいて「マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理を CLI端末へのコマンド入力 一撃で 終わらせること」(USP友の会会長・上田隆一による定義)である。
この技術を持つ人物を シェル芸人 という。

 

https://qiita.com/t_nakayama0714/items/bfe4852e0535858ee662

www.usptomo.com

 

 

一時期話題になっていたojichatやその派生系のhimechatも優秀なシェル芸人達によって日々生み出されています。わくわくしますね!

 

僕も偉大な先輩方に近づけるよう、日々精進しております。

 

そんなシェル芸界隈ではう〇こをあらゆる手段で出力することが流行っています。(大嘘…でもないのか?)

 

 これは一番最近観測したkingunko

 

しかし、bashでうんこを出力するなんてありきたりなことじゃ感動が薄れるのではないか?と思い、

そうだPowershellで書いたら面白いんじゃないか

となり今にいたります。

 

ただし、ただpowershellを使っても

 

echo うんこ

 

とかで終わってしまっては面白くないので、今回はbase64で書いたうんこをデコードするコマンド群を書いて、さらにそのワンライナー自体を難読化させることを目標に作ることにしました。

すなわち、目標としては

echo "44GG44KT44GT" | base64 -dと似たようなことをコマンドレットのみで実現したい

 となります。

 

ちなみに、熟練したシェル芸人は目でbase64をデコードするといわれています。恐ろしいですね。

 

本文

概要

これを実現する為に必要なことは ざっくりまとめると以下の二つです。

  1. 任意のアルファベットを生成する
  2. それを連結させてeval()する

ここで言うevalとは、bashで用いられるものであり主な用途は

evalコマンドが威力を発揮する場面は2つあります。1つはあるコマンドが出力した文字列自体をコマンドとして実行すること、もう1つは入れ子になった)変数を評価することです。

です。

www.atmarkit.co.jp

 

これはpowershellではInvoke-Expressionと呼ばれるコマンドレットで実現可能です。これで、引数に指定した文字列をコマンドとして実行することを可能にします。

また、これはiexでaliasされています。

iex自体を実行するには実行演算子&を用います。

 

"[char]文字コード番号 + [char]文字コード番号 + ... | iex" | &"iex"

イメージはこんな感じ。

 

さて、任意のアルファベットを用意する方法ですが、int型の数値をchar型

にキャストできることを利用します。

 

例えば、"[char]72"をiexにかけてやると、"H"が出力されます。

 

つまり、文字コードを作り、それをなんらかの手段で作ったcharでキャストしてiexにかければいいわけです。

 

数値を作る

 

powershellでは${}で囲むとほとんどの記号を変数にできる性質を持ちます。

 

まず初めに、変数をすべて記号化する為、以下のようにします。

${;}=+$()
${=}=${;} #0
${+}=++${;} #1
${@}=++${;} #2
${.}=++${;} #3
${[}=++${;} #4
${]}=++${;} #5
${(}=++${;} #6
${)}=++${;} #7
${&}=++${;} #8
${|}=++${;} #9

 

 

まず変数${;}を用意します。$()は空の部分式で、$nullと等しくなります。これに+を付けると$nullがintにキャストされて0になります。それを${;}に代入。

 

そしてインクリメントして完成です。

 

”Char”を作る

 "r"は"True"から、"c","h","a"は連想配列から生み出します。

 

Trueは自動変数"$?"から生成することができます。"$?"は前のコマンドが正常終了されればTrue,異常終了されればFalseを吐き出すものです。

 

ここでは、Trueの二文字目のみを取得したいので、文字列にインデックスを付けるとそのインデックスの場所にある文字を取得できることを利用して

 

"$?[${+}]" 

このように記述します。

 

次に"c","h","a"ですが、空の連想配列@{}を用意して

これを部分式$()の中に格納し、"$(@{})"としてstringにキャストすると"System.Collections.Hashtable"という文字列が得られることを利用して実現します。

 

以上をまとめると

${"}="["+"$(@{})"[${)}]+"$(@{})"["${+}${|}"]+"$(@{})"["${@}${=}"]+"$?"[${+}]+"]"

 

 

${"} #char

 

"iex"を作る

"i"と"e"は"System.Collections.Hashtable"に含まれているので、xをどうにかして工面する方針で考えます。

 

これはSystem.stringでカバーします。

System.stringにはInsert()というメソッドがあり、

string Insert(int startIndex, string value)

 ここにxが含まれているので、このxをなんとかして取り出したいです。

 

これは、

PowerShell

<オブジェクト>.メソッド名

とするとメソッド情報が格納されたPSMethodオブジェクトを取得できることを利用して解決できます。またPSMethodオブジェクトをstringにキャストするとそのメソッドのシグネチャが文字列として得られます。

 

つまり

$method="".insert "$method"

このようなコードを記号で記述すればいいわけです。

”insert”については、これまでの文字にすべて含まれているので

 

${;}="".("$(@{})"["${+}${[}"]+"$(@{})"["${+}${(}"]+"$(@{})"[${=}]+"$(@{})"[${[}]+"$?"[${+}]+"$(@{})"[${.}]) 

 

これで${;}にはPSMethodオブジェクトが格納されて、 "${;}"["${@}${)}"]とすれば"x"が得られます。

 

以上をまとめて

${;}="$(@{})"["${+}${[}"]+"$(@{})"[${[}]+"${;}"["${@}${)}"]

 

 

${;} #iex

 

組み立てる 

 後は実行したいコードを文字コードに変換するだけです。

今回はpowershell上でbase64デコードによりうんこを表示するので、ワンライナーは以下のようになります。

 

$b64txt="44GG44KT44GT";$byte = [System.Convert]::FromBase64String($b64txt);$txt = [System.Text.Encoding]::UTF8.GetString($byte);write-output $txt

 

 

これを一度手元で文字コード番号に変換してあげる必要があります………

 

当然全て手で変換するのは骨が折れるので簡易的なスクリプトを組みました。

 

$str = [string]"hogefugapiyo";( [char[]]"$str" | %{'${"}'+ ([int]$_ -replace "0",'${=}' -replace "1",'${+}' -replace "2",'${@}' -replace "3",'${.}' -replace "4",'${[}' -replace "5",'${]}' -replace "6",'${(}' -replace "7",'${)}' -replace "8",'${&}' -replace "9",'${|}') }) -join '+'

 

本当はparam関数とか使ってもっと賢いスクリプトファイル作っていろいろやりたかったけど上手くいかなかったので手打ちしました。。。。。

 

 

出力

以下がコードで

${;}=+$();${=}=${;};${+}=++${;};${@}=++${;};${.}=++${;};${[}=++${;};${]}=++${;};${(}=++${;};${)}=++${;};${&}=++${;};${|}=++${;};${"}="["+"$(@{})"[${)}]+"$(@{})"["${+}${|}"]+"$(@{})"["${@}${=}"]+"$?"[${+}]+"]";{;}="".("$(@{})"["${+}${[}"]+"$(@{})"["${+}${(}"]+"$(@{})"[${=}]+"$(@{})"[${[}]+"$?"[${+}]+"$(@{})"[${.}]);${;}="$(@{})"["${+}${[}"]+"$(@{})"[${[}]+"${;}"["${@}${)}"];
"${"}${.}${(}+${"}${|}${&}+${"}${]}${[}+${"}${]}${@}+${"}${+}${+}${(}+${"}${+}${@}${=}+${"}${+}${+}${(}+${"}${(}${+}+${"}${.}${[}+${"}${]}${@}+${"}${]}${@}+${"}${)}${+}+${"}${)}${+}+${"}${]}${@}+${"}${]}${@}+${"}${)}${]}+${"}${&}${[}+${"}${]}${@}+${"}${]}${@}+${"}${)}${+}+${"}${&}${[}+${"}${.}${[}+${"}${]}${|}+${"}${.}${(}+${"}${|}${&}+${"}${+}${@}${+}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${.}${@}+${"}${(}${+}+${"}${.}${@}+${"}${|}${+}+${"}${&}${.}+${"}${+}${@}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${+}${=}${|}+${"}${[}${(}+${"}${(}${)}+${"}${+}${+}${+}+${"}${+}${+}${=}+${"}${+}${+}${&}+${"}${+}${=}${+}+${"}${+}${+}${[}+${"}${+}${+}${(}+${"}${|}${.}+${"}${]}${&}+${"}${]}${&}+${"}${)}${=}+${"}${+}${+}${[}+${"}${+}${+}${+}+${"}${+}${=}${|}+${"}${(}${(}+${"}${|}${)}+${"}${+}${+}${]}+${"}${+}${=}${+}+${"}${]}${[}+${"}${]}${@}+${"}${&}${.}+${"}${+}${+}${(}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${=}+${"}${+}${=}${.}+${"}${[}${=}+${"}${.}${(}+${"}${|}${&}+${"}${]}${[}+${"}${]}${@}+${"}${+}${+}${(}+${"}${+}${@}${=}+${"}${+}${+}${(}+${"}${[}${+}+${"}${]}${|}+${"}${.}${(}+${"}${+}${+}${(}+${"}${+}${@}${=}+${"}${+}${+}${(}+${"}${.}${@}+${"}${(}${+}+${"}${.}${@}+${"}${|}${+}+${"}${&}${.}+${"}${+}${@}${+}+${"}${+}${+}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${+}${=}${|}+${"}${[}${(}+${"}${&}${[}+${"}${+}${=}${+}+${"}${+}${@}${=}+${"}${+}${+}${(}+${"}${[}${(}+${"}${(}${|}+${"}${+}${+}${=}+${"}${|}${|}+${"}${+}${+}${+}+${"}${+}${=}${=}+${"}${+}${=}${]}+${"}${+}${+}${=}+${"}${+}${=}${.}+${"}${|}${.}+${"}${]}${&}+${"}${]}${&}+${"}${&}${]}+${"}${&}${[}+${"}${)}${=}+${"}${]}${(}+${"}${[}${(}+${"}${)}${+}+${"}${+}${=}${+}+${"}${+}${+}${(}+${"}${&}${.}+${"}${+}${+}${(}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${=}+${"}${+}${=}${.}+${"}${[}${=}+${"}${.}${(}+${"}${|}${&}+${"}${+}${@}${+}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${+}+${"}${]}${|}+${"}${+}${+}${|}+${"}${+}${+}${[}+${"}${+}${=}${]}+${"}${+}${+}${(}+${"}${+}${=}${+}+${"}${[}${]}+${"}${+}${+}${+}+${"}${+}${+}${)}+${"}${+}${+}${(}+${"}${+}${+}${@}+${"}${+}${+}${)}+${"}${+}${+}${(}+${"}${.}${@}+${"}${.}${(}+${"}${+}${+}${(}+${"}${+}${@}${=}+${"}${+}${+}${(}|${;}"|&${;};

 

出力結果がこちら

 

f:id:taikohaijin44:20191221185729p:plain

unko

楽しいですね() 

 

 

過程での苦労した点、結果など 

1.変数に文字を代入する時、ダブルクォーテーションが入れ子になってめんどくさい

$str = [string]" $b64txt="44GG44KT44GT""

 

こうなると正しく認識されないわけですね。このバグ探しを全部エンコードし終わった後にやるから精神がすり減る。病むコレ。視界がバグる。

 

まぁエスケープシーケンスはよく使うから詳しくなれてよかった。

ちなみにPowerShell は、エスケープシーケンスとしてバッククォート(`)を使用します。

 

 2.変数をエンコードする時に、その変数自身を参照?してからエンコードする

 

つまり、$aを翻訳したくとも、その$aの中身を翻訳しようとするのでバグる。nullが入ってる。

 

3.エディタ使わずにやったので疲れた。

 

今度からおとなしくVScode使います。

 

 

参考記事様

https://web.archive.org/web/20190420195441/http://perl-users.jp/articles/advent-calendar/2010/sym/11

https://binary-pulsar.hatenablog.jp/entry/2018/09/01/000000

https://win.just4fun.biz/?PowerShell/PowerShell%E3%81%A7Base64%E3%81%AE%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89%E3%81%A8%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89