😀

難読化シェル芸の解読: 1

2021/09/28に公開

はじめに

全体

post
# https://twitter.com/hiro_poke91/status/1218084119504048128
.>&$
"$(<$)"
! __="${_##*. ??}"
$?
___=${__:$_$_:_}${__:~_<<_:_}${__:~_:_}${__:_:_}${__:~_-_:_} __=${__::_}${__:$#$_$#:_}${__:~_<<_:_}${__:$_$#:_}
$___ ${__^^}
(($?=$?))>&$
_=($(<$))
${_[${##}$#>>++_]:~$#}${_[$_$#/++_]::_}${_[_**++_]:~$#}

返り値は

result
# https://twitter.com/minyoruminyon/status/121808415852362957
Fri Jan 17 17:14:26 JST 2020

どうやらロケールが英語圏のdateらしい
???なんだこれ おかしいよ

解読開始

.>&$

  • これを実行すると$というファイルに.コマンドのエラー文が書き込まれる
$
/task.sh: 1 行: .: ファイル名が引数として必要です
.: 使用法: . filename [arguments]
  • >&StdOut+StdErrのリダイレクト

"$(<$)"

  • cmd <filecmdfileの内容を入力する
  • ここでは<$のコマンドがないので、catを入力先としているので$(cat $)と同値
  • そのため"$の中身"が実行され変数_(後述)に格納される(Thanks:@qwertanusさん)
  • ちなみにファイル入出力はどこでやっても良い
example
echo unko >unko
>unko echo unko
echo >unko unko

! __="${_##*. ??}"

  • 変数_は現在実行しているシェルを格納する(コマンド名とかshファイル名)
  • 前の行で実行された$の中身が変数_に格納されている
  • そしてこの行を実行すると変数__lename [arguments]が代入される
  • ${v##exp}vの値のexpにマッチした部分以降を最長一致で取り出す展開
  • ここで入力されたファイル$*. ??がマッチするのは以下
matched
/task.sh: 1 行: .: ファイル名が引数として必要です
.: 使用法: . fi
  • これ以降のlename [arguments]が変数__に代入される
  • 行頭の!は代入後の終了ステータス0を否定/反転させ1にしている
  • よって次行の$?1となる(Thanks:@qwertanusさん)

$?

  • 変数?は直前のコマンドの終了ステータスの参照
  • ここでは1が返る
  • そして1という名前のコマンドは当然存在しないので127が変数?に格納される
  • またここで変数_1が格納される(次で使用)

___=${__:$_$_:_}${__:~_<<_:_}${__:~_:_}${__:_:_}${__:~_-_:_}

  • $_1, $__lename [arguments](18文字)
  • これを実行すると変数__unsetが代入される
  • ${v:offset:length}vの値のoffsetのインデックスからlength文字分切り出す展開
  • Pythonだとv[offset:length]みたいな
  • ~はビット反転, <<は左ビットシフト
  • ___=${__:11:1}${__:-4:1}${__:-2:1}${__:1:1}${__:-3:1}
  • よって___=unsetとなる

__=${__::_}${__:$#$_$#:_}${__:~_<<_:_}${__:$_$#:_}

  • $_1, $__lename [arguments](18文字)
  • 変数#は関数の引数の数で、今は関数内でないので0が返る
  • 上と同様に、__=${__::1}${__:010:1}${__:-4:1}${__:10:1}
  • 010(8)=8(10)(Thanks:@hiroさん)
  • offsetを省略すると0番目からとなる
  • よって__=langとなる

$___ ${__^^}

  • ${v^^}はvの値の小文字を全て大文字に変換する展開(upcase)
  • unset LANGとなる
  • シェル芸Botのロケール(LANGの設定値)はja_JP.UTF-8
  • 解除するとコマンドのデフォルトでのロケールで実行されるのでdateが英語に

(($?=$?))>&$

  • (())は算術演算を行う
  • ここでは$?=$?0=0となり、00の代入を行う
  • 非変数への代入は不正なので、エラーが起き、そのメッセージがファイル$に書き込まれる
$
/task.sh: line 7: ((: 0=0: attempted assignment to non-variable (error token is "=0")

_=($(<$))

  • var=()は配列の宣言
  • ここでは$の内容がスペースで区切りで配列_に代入される
  • 参照は${arr[index]}で行う

${_[${##}$#>>++_]:~$#}${_[$_$#/++_]::_}${_[_**++_]:~$#}

  • _=('' line 7: ((: 0=0: attempted assignment to non-variable (error token is "=0"))
  • 0番目は空文字列(Thanks:@hiroさん)
  • ${v:offset}はvの値のoffsetのインデックスから末尾までを切り出す展開
  • ++はインクリメント, >>は右ビットシフト
terminal
root@628d738436d9:/# echo $[++a]
1
root@628d738436d9:/# echo $[++a]
2
root@628d738436d9:/# echo $[++a]
3
  • ${#v}はvの文字列長を返す展開で、vが配列なら${#v[*]}で配列の要素数が返る
  • ${##}$#の長さ1が返る
  • ${_[10>>1]:-1}${_[10/2]::2}${_[2**3]:-1}
  • ${_[5]:-1}${_[5]::2}${_[8]:-1}dateが返り実行される

結論

こわ

追記:あわせて読みたい本質情報群

GitHubで編集を提案

Discussion