日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

符号化処理芸人衆

シェル芸ではテキスト処理だけでなくバイナリの符号化処理とかも扱うことが出来る。こういう処理がサラッと出来るようになるとヤバい人ステキな人と危険視尊敬されること間違いなし。

元々のキッカケが、MicrosoftエバンジェリストちょまどさんのTwitterつぶやき。

文字列をバイナリのビット文字列にしたってことかー。シェル芸でちょっくら解読してみよう。16進数に変換してxxdコマンドに突っ込もう。まずはその準備。

$ echo 01010101 01110011 01100101 00100000 01011000 01100001 01101101 01100001 01110010 01101001 01101110 00101110 | tr -d ' ' | sed 's/^/obase=16;ibase=2;/'

obase=16;ibase=2;010101010111001101100101001000000101100001100001011011010110000101110010011010010110111000101110

ここからbcコマンドに突っ込んで16進数に変換

$ echo 01010101 01110011 01100101 00100000 01011000 01100001 01101101 01100001 01110010 01101001 01101110 00101110 | tr -d ' ' | sed 's/^/obase=16;ibase=2;/' | bc

5573652058616D6172696E2E

あとはxxdコマンドに突っ込んでバイナリ変換。ということでみんなXamarin使おう!

$ echo 01010101 01110011 01100101 00100000 01011000 01100001 01101101 01100001 01110010 01101001 01101110 00101110 | tr -d ' ' | sed 's/^/obase=16;ibase=2;/' | bc | xxd -p -r

Use Xamarin.

私もモノマネって01のビット列でiOS10のダウンロード失敗しまくる愚痴をTwitterに吐いていた。

PerlやRubyでpack使う解答は@ebanさんより。

そしてしばらくすると@grethlenさんからこんな暗号?問題が出ていた。

んんん???すぐには分からくていろいろ考えた・・・2文字ずつ切ってみると気が付いたことが。これは1が3つ、0が2つ、1が4つ、0が4つってこと??

$ echo 1302140411021101140213011103110511011103110211 | fold -w 2

13
02
14
04
....

じゃあその前提で復号化してみよう。awkでビット列を復元する。FS=を指定することで1文字ずつフィールド分割して処理。

$ echo 1302140411021101140213011103110511011103110211 | fold -w 2 | awk '{for(i=1;i<=$2;i++){printf $1}}' FS=

111001111000010010111100111010001000001010001001

あとは上記の通り、16進数変換してxxdコマンドでバイナリ変換。ステルス飯テロ・・・

$ echo 1302140411021101140213011103110511011103110211 | fold -w 2 | awk '{for(i=1;i<=$2;i++){printf $1}}' FS= | sed 's/^/obase=16;ibase=2;/' | bc | xxd -p -r

焼肉

ちなみにこの01と続く数のペアの符号化をランレングス圧縮(連長圧縮)というらしい。なるほろ。

逆に文字列をランレングス圧縮する方法。まずは16進数にダンプしてbcコマンドで2進数に変換する。bcコマンドに16進数を突っ込む時は大文字にする必要があるため、xxdコマンドに-uオプションを付けるか別途trコマンドなどで変換する。

$ echo -n おでん | xxd -u -p | sed 's/^/obase=2;ibase=16;/' | bc

11100011100000011000101011100011100000011010011111100011100000101001\
0011

ビット列が80文字を超えると折り返されてしまうので、trコマンドでバックスラッシュと改行を消して1文字ずつ改行する。

$ echo -n おでん | xxd -u -p | sed 's/^/obase=2;ibase=16;/' | bc | tr -d '\n\' | grep -o .

1
1
1
.....
0
1
1

ここからuniq -cを使って1と0が続く数を数えればいい。あえて並べ替えないでuniq処理をするのがポイントだ。

$ echo -n おでん | xxd -u -p | sed 's/^/obase=2;ibase=16;/' | bc | tr -d '\n\' | grep -o . | uniq -c 

   3 1
   3 0
   3 1
.....
   1 1
   2 0
   2 1

あとはawkを使って並べて完成。

$ echo -n おでん | xxd -u -p | sed 's/^/obase=2;ibase=16;/' | bc | tr -d '\n\' | grep -o . | uniq -c | awk '{printf $2$1}'

1303130612031101110113031306120111021603130511011102110212

Tukubaiのcountコマンド使うならこんな感じ。

$ echo -n おでん | xxd -u -p | sed 's/^/obase=2;ibase=16;/' | bc | tr -d '\n\' | grep -o . | count 1 1 | tr -d ' \n'

1303130612031101110113031306120111021603130511011102110212

さてここまで考えた後、xxdコマンドのオプションを調べていると-bオプションなるものが。

さらにxxdコマンドに-cオプションがあった。

そしてxxdコマンドの-bオプションと-cオプションを活用した事例が早速登場。

つまり0と1でマッピングしてしまえということ。このビットパターンになる文字列を地道に探したということか??

$ echo 'In48FEBACHw8CEACCEBCCH48' | base64 --decode | xxd -b -c 3

0000000: 00100010 01111110 00111100  "~<
0000003: 00010100 01000000 01000000  .@@
0000006: 00001000 01111100 00111100  .|<
0000009: 00001000 01000000 00000010  .@.
000000c: 00001000 01000000 01000010  .@B
000000f: 00001000 01111110 00111100  .~<

後は余計な列を消し、1をyへ、0をスペースへ変換。

$ echo 'In48FEBACHw8CEACCEBCCH48' | base64 --decode | xxd -b -c 3 | awk '$1="";$NF="";1' | sed 'y/01/ y/'

   y   y   yyyyyy    yyyy   
    y y    y        y       
     y     yyyyy     yyyy   
     y     y             y  
     y     y        y    y  
     y     yyyyyy    yyyy  

追記

この記事を書いた後、上記のビットパターンの作り方を教えていただいた。なるほどbannerコマンドか。そんなのあった。

そしてこれらをキッカケに、様々な符号化処理や暗号処理がTwitter上に流れだしたのであった。@grethlenさんがTogetterにまとめてくれているのでこちらをご覧あれ。コワクナイヨ。多分・・・きっと・・・キット・・・KITTO・・・

togetter.com

2016/09/24 追記

PowerShellで解いてくれた方が!反響ありがたや。スクリプトブロックがbeginとendもあってawkっぽい。PowerShellの文字コードってUTF-8なのか。ややこしい処理は.NET Frameworkの力を借りる。 stknohg.hatenablog.jp