難読化シェル芸に使えそうなテクニック集
#シェル芸 #難読化シェル芸 #bash
まえがき
基本的にこの記事にかかれているテクニックはすべてbashを使う前提
Bashで難読化しながら数値を得る方法
Bashで難読化しながらアルファベットを得る方法
数値演算
code:calc.sh
echo $(( 2 + 3 )) # 5
# 10進数でのビット演算 左シフト
echo $(( 1 << 1 )) # 2
echo $(( 1 << 1 << 1 )) # 4
# こうもかける
echo $ 1 << 1 << 1 # 4
# 8進数でのビット演算
# 0で始まる数値は8進数
echo $ 010 >> 1 # 4
echo $[$[]$$$/$$$[]>>$$$/$$] # 4
Zshでは 0 で始まる数値を10進数として計算するのでBashと計算結果が変わる場合がある
ZshとBashでシフト演算の優先度が違うのでBashと計算結果が変わる場合がある
code:bash_zsh.sh
echo $5/5 << 5/5 << 5/5
# Bash -> 4
# Zsh -> 0
日付を出力する例
code:yyyymmdd.sh
__="$([)$?$[]$$$/$$$(($[]$$$/$$$[]+$$/$$))/$(_)${?/??}/$([)$?$(($$/$$+$$/$$+$$/$$))"
echo 今日は$__です。
# -> 今日は2019/7/23です。
Bashの特殊変数
Qiita - Shell 特殊変数
code:vars.sh
x=1234
echo $x # 変数
echo ${#x} # 変数の値の文字列長
echo $$ # プロセスID
echo $? # 直前のコマンドの終了コード
echo $- # 現在のBashに適用されているオプション
echo $# # 引数の数
echo ${##} # $#変数の値の文字列長
# 第一引数
echo $1
# ${$$$/$$} とは書けないので、第一引数の取得を難読化するには以下のように書く
echo ${@:1:1}
echo ${@:$$$/$$:$$$/$$}
コマンドのパスを得る
パス名展開(Pathname Expansion)
code:path.sh
echo /???/????
echo /???/???/??????
# base64
echo /???/???/????64
echo /???/???/????$[$[]$$$/$$$[]*$[]$$$/$$$[]]
# date
echo /???/??t?
echo /*/*/????64
パス名展開にはレンジで出現する文字を指定できる
code:path2.sh
echo bin/a-z # -> w
echo bin/a-za-z # bin/配下の小文字アルファベット二文字のコマンドが出力
echo bin/@-}@-} # ASCIIコード表によると大文字アルファベット、小文字アルファベットの次に}が出現する
echo bin/[]-{}[][]-{}[] # 紛らわしいけれどコレも
コマンドのパスから特定のコマンドを得る
code:get_path.sh
# 配列として変数に格納
cmds=(/???/??t?)
# /bin/date /bin/stty
# インデックスで取得
echo ${cmds0}
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@} # 全て取得
echo ${x0} # 先頭の要素
echo ${x1} # 2つ目の要素
echo ${x[${#x@}-1]} # 末尾の要素
配列定義にはブレース展開やコマンド置換が使える
code:array_def.sh
x=({a..z})
echo ${x@}
# a b c d e f ... z
x=($(seq 20))
echo ${x@}
# 1 2 3 4 ... 20
文字列の部分取得
パラメータ展開という
【シェル芸人への道】Bashの変数展開と真摯に向き合う
code:strings.sh
x=1234date5678
echo ${x} # 全て取得
echo ${x::4} # 先頭から4文字取得
echo ${x:4} # 先頭から4文字以降取得
echo ${x:4:4} # 先頭から4文字目から4文字取得
echo ${x:$[$$$/$$<<$$$/$$<<$$$/$$]:$[$$$/$$<<$$$/$$<<$$$/$$]}
パスから文字列を取り出す
code:strings2.sh
__=(/???/??/$[]) # /dev/fd/0
echo ${__:6:1}
echo ${__:${#__}-3:1}
echo ${__:${#__}-$[$$$/$$+$$$/$$+$$$/$$]:$$$/$$}
# -> 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
tr ':upper:' ':lower:' <<< DATE # -> 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 -eq 1 ; };
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