難読化シェル芸に使えそうなテクニック集
まえがき
基本的にこの記事にかかれているテクニックはすべてbashを使う前提 数値演算
code:calc.sh
echo $(( 2 + 3 )) # 5
# 10進数でのビット演算 左シフト
echo $(( 1 << 1 )) # 2
echo $(( 1 << 1 << 1 )) # 4
# こうもかける
# 8進数でのビット演算
# 0で始まる数値は8進数
Zshでは 0 で始まる数値を10進数として計算するのでBashと計算結果が変わる場合がある ZshとBashでシフト演算の評価順序が違うのでBashと計算結果が変わる場合がある code:bash_zsh.sh
# Bash -> 4
# Zsh -> 0
日付を出力する例
code:yyyymmdd.sh
__="$([)$?$[]$$$/$$$(($[]$$$/$$$[]+$$/$$))/$(_)${?/??}/$([)$?$(($$/$$+$$/$$+$$/$$))" echo 今日は$__です。
# -> 今日は2019/7/23です。
Bashの特殊変数
code:vars.sh
x=1234
echo $x # 変数
echo ${#x} # 変数の値の文字列長
echo $$ # プロセスID
echo $? # 直前のコマンドの終了コード
echo $- # 現在のBashに適用されているオプション
echo $# # 引数の数
echo ${##} # $#変数の値の文字列長
# 第一引数
echo $1
# ${$$$/$$} とは書けないので、第一引数の取得を難読化するには以下のように書く echo ${@:1:1}
コマンドのパスを得る
パス名展開(Pathname Expansion) code:path.sh
echo /???/????
echo /???/???/??????
# base64
echo /???/???/????64
# date
echo /???/??t?
echo /*/*/????64
パス名展開にはレンジで出現する文字を指定できる
code:path2.sh
echo bin/a-za-z # bin/配下の小文字アルファベット二文字のコマンドが出力 echo bin/@-}@-} # ASCIIコード表によると大文字アルファベット、小文字アルファベットの次に}が出現する echo bin/[]-{}[][]-{}[] # 紛らわしいけれどコレも
コマンドのパスから特定のコマンドを得る
code:get_path.sh
# 配列として変数に格納
cmds=(/???/??t?)
# /bin/date /bin/stty
# インデックスで取得
echo ${cmds[$[]]}
数値から文字を取得する
code:char.sh
echo $'\144\141\164\145' # -> date
echo $'\U64\U61\U74\U65' # -> date
配列操作
code:array.sh
x=(0 1 2 3)
echo ${x[${#x@}-1]} # 末尾の要素 code:array_def.sh
x=({a..z})
# a b c d e f ... z
x=($(seq 20))
# 1 2 3 4 ... 20
文字列の部分取得
code:strings.sh
x=1234date5678
echo ${x} # 全て取得
echo ${x::4} # 先頭から4文字取得
echo ${x:4} # 先頭から4文字以降取得
echo ${x:4:4} # 先頭から4文字目から4文字取得
パスから文字列を取り出す
code:strings2.sh
__=(/???/??/$[]) # /dev/fd/0
echo ${__:6:1}
echo ${__:${#__}-3:1}
# -> d
文字列の部分削除
変数名をスラッシュで区切って、後ろに書いた文字が最初にマッチした箇所を削除する
code:delete.sh
x=12date34
echo ${x/1} # -> 2date34
echo ${x/d} # -> 12ate34
echo ${x/12} # -> date34
echo ${x/??} # -> date34 ? だとすべての文字とマッチする
文字列のエンコード・デコード
code:encode.sh
echo date | base64
# -> ZGF0ZQo=
code:decode.sh
echo ZGF0ZQo= | base64 -d
# -> date
base64 -d <<< ZGF0ZQo=
コマンドにテキストを渡す
code:cmds.sh
# ヒアストリング
base64 -d <<< ZGF0ZQo= # -> date
# ヒアドキュメント 複数行のテキストを渡す
cat << EOS
1行目
2行目
EOS
echo ZGF0ZQo= > a.txt
base64 -d < a.txt
# コマンド置換
base64 -d <(cat a.txt)
paste a.txt a.txt
paste <(echo date | grep -o .) <(echo date | tr ':lower:' ':upper:' | grep -o .) code:brace_expansion.sh
echo {朝,昼,晩}
echo {0..9}
echo {a..z}
echo {A..z}
関数定義
+や-は変数名には使えないけれど、関数名には使用できる
他にもエスケープが必要だけれど、特殊な文字を関数名に使用できる
code:func.sh
+() { echo "+" ;};
+
-() { echo "-" ;};
-
@() { echo "@" ;};
@
_() { echo "_" ;};
_
*() { echo "*" ;};
\*
/() { echo "/" ;};
\/
?() { echo "?" ;};
\?
こんなふうに関数名に記号を使うこともできる。この場合はエスケープ不要
code:is_1?.sh
is_1? 1
echo $? # 0
is_1? 2
echo $? # 1
ファイルへのリダイレクト
code:redirect.sh
# エラーも合わせてログに出力
x > err.log 2>&1
x >& err.log
一時変数$_を使って値を渡す
$_には直前のコマンドの最後の引数が格納される
code:sh
echo a b c d
echo $_ # -> d
: a b c d
echo $_ # -> d
# aコマンドが存在しなくて失敗しても $_ 変数にはセットされる
a b c d
echo $_ -> d
コマンドが失敗しても$_変数は更新されるということは難読化につかえる
code:sh
cd /tmp
mkdir work
cd work
touch a.txt b.txt c.txt
# pathname expansion で * は a.txt b.txt c.txt に展開され、
# a.txt コマンドに b.txt c.txt を引数としてわたそうとする。
#
# a.txt は実行可能ファイルではないため、コマンドの実行には失敗する。
# しかし、 $_ 変数は更新される
*
echo $_ # -> c.txt