ふるつき

v(*'='*)v かに

シェル芸botの宣伝をさせてください

 この記事はShell Script Advent Calendar の19日目に向けて書かれた記事です。参加する気はなかったんですが、ブログでシェル芸botについて言及したことがなかったのと、ふとカレンダーを見たら空いてたのとで入れてもらうことにしました。よろしくお願いします。


シェル芸botについて

 シェル芸bot ( @minyoruminyon )をご存知でしょうか。シェル芸botは、「TLに現れた #シェル芸 または #危険シェル芸 とタグのついたツイートについて、そのツイートをシェルスクリプトとしての実行を試み、正常終了すれば結果を引用でツイートする」というbotになります。「シェル芸」とはなんぞやという方は こちら の定義や #シェル芸 - Twitter Search の例などをご参照下さい。

 シェル芸botは現在 386 フォロワーを獲得しており、生まれてから 2633 のシェル芸を実行してきました。このシェル芸botを作ったのが私ふるつきで、せっかくなのでセールストークをさせていただきます。

 シェル芸botが生まれたのは 2017年の 6月であると、 シェル芸史 にあります。以前から、 base64 encoded なTweetをする界隈に身をおいていたこともあり、ある程度の「シェル芸」というワードに親しんでいた私はしかし、TLに流れてくる不可思議な記号列=シェル芸をターミナルに貼り付けて実行するのが面倒&怖かった憶えがあります。そこで、ある種のサンドボックスとして、シェル芸botを作成することにしました。もちろん、シェル芸界隈のノリの良さのようなものを目にしており、これはイケるだろうと思っていなかったわけがありませんが。

 斯くしてシェル芸botはTL上に生を受け、瞬く間にシェル芸人たちの間に広がった、はずです。はじめは不慮の停止が数度あった気がしますが、現在は安定して稼働を続けています。シェル芸botはユーザフレンドリーなbotを目指しているので、シェル芸人の皆様の活動に合わせて日々利用できるコマンドが追加されています。

@paiza_run との関係

 TL上のプログラムを実行するとくれば、まず思い浮かぶのが paiza_run です。シェル芸botと動きは丸かぶりですが、シェル芸botはどちらかといえば、「ツイートしたシェル芸が思いがけず実行され、結果が見られる」という public な向きのbot です。

 paiza_run は凍結されてしまいましたが、以前にはコラボレーションしたこともありました。

togetter.com

シェル芸bot に触れてみよう

 大したハードルもないですが、シェル芸bot に初めて触る方向けのチュートリアルです。

  1. シェル芸botをフォローしましょう
  2. シェル芸botのフォローバックを待ちましょう(手動です)
  3. echo-sd シェル芸楽しい!! #シェル芸 とツイートします

 これであなたもシェル芸人です。巷では様々なシェル芸が飛び交っていますので眺めるも解読するも創り出すも良しです。お楽しみ下さい。  

シェル芸bot のこれから

 ある程度安定してきたシェル芸botですが、これからも「あんなコマンドがほしい」「こんなコマンドを作った」が尽きることはないと思うので、ガンガンアップデートをかましていきたいです(何かのタイミングで私の目に止まれば可及的に速やかに更新致します)。そしてできれば私もシェル芸人の仲間入りを果たしたい……!

 ところでシェル芸bot さんの顔グラがないのが味気ないので募集しています。よろしくお願いします。

高専セキュリティコンテスト #SCKOSEN で優勝した

奈良高専は insecure というチームで、 theoldmoon0602、 thrust2799、 kyumina、 miwpayou の四人で、高専セキュリティコンテストに参加しました。現地行きたかったけど申し込み失敗しました。

これは二日間(実質24時間程度)の Jeopardy + King of the Hill なCTFで、 Jeopardy が 22問くらい、 KoH が 2問ありました。 KoH では 30点が 10分ごとに与えられます。

私チーム insecure はチームワークを遺憾なく発揮して、 3930 点で 1位(35チーム中)でした。私はそのうち、 1400 + 630 点を入れましたので、解いたものについて 簡単な Write up をします。

f:id:Furutsuki:20171022123605p:plain

jeopardy Binary[100] フラグを答えろ

バイナリが降ってくるので strings する

jeopardy Binary[500] OreNoFS

途中で追加された問題。31M くらいのバイナリが渡されて、これが独自FSらしい。めっちゃよなべしたけど解けなかった。最終的には解いた。

バイナリは GPT でパーティショニングされているということ。1クラスタ4096byte 単位で管理されていること。AllocationTableのサイズ、DirectoryEntry 構造体が示されていた。

最初は binwalk などしていたが、 何かしらの zip ファイルが存在していて、 flag.png_MAXOSX/.flag.png が入ってることしかわからず、 unzip はできなかった。

二日目になってやっと参考になるサイトを見つけて ( http://memes.sakura.ne.jp/memes/?page_id=2303 ) 、AllocationTable がどのような存在かを理解したので、ごちゃっとパーサを書いたらunzippable な zip を取り出せました。

方針としては、

  1. DirectoryEntry をみつける
  2. これは ほしいzipのEntryだって知ってるので、 offset を見て、バイナリを読み、AllocationTable から次のクラスタのオフセットを手に入れる
  3. これ以上なくなったらそれを吐く

です。

import struct
import os
import codecs
import sys
from io import SEEK_CUR,SEEK_SET

LBA_SIZE = 512


f = open("raw.dmg", "rb")

f.seek(512, SEEK_CUR) # skip mbr
f.seek(512, SEEK_CUR) # gpt header (primary)

f.seek(32, SEEK_CUR) # partition entry

first_sector = struct.unpack('<Q', f.read(8))[0]
last_sector = struct.unpack('<Q', f.read(8))[0]
f.seek(8, SEEK_CUR)

partition_name = f.read(72)
partition_name_str = codecs.decode(partition_name, encoding='utf_16_le')
if len(partition_name_str)>0:
    print("------------")
    print("first sector: {}".format(first_sector))
    print("last sector:  {}".format(last_sector))
    print("name: {}".format(partition_name_str))
    print()

f.seek( first_sector*LBA_SIZE, SEEK_SET)



AT = f.tell()
print("ALLOCATION TABLE IS {:0X}".format(AT))

allocationTable = []
for _ in range(8192):
    v = f.read(2)
    v = struct.unpack("<H", v)[0]
    allocationTable.append(v)

print("----------------")
print("Allocation Table");
for i, v in enumerate(allocationTable):
    if v == 0:
        continue
    # print("[{0}]   {1:0X}( {1} )".format(i, v));
    pass

DIRENTS = f.tell()
print("DIRECTORY ENTRIES AT: {:0X}".format(DIRENTS))

binvalue = []

while f.tell() < (last_sector+1)*LBA_SIZE:
# read directory entry
    tell = f.tell()
    magic = f.read(1)
    if magic != b'\x0f':
        f.seek(31, SEEK_CUR)
        continue

    name = f.read(8)
    ext = f.read(3)
    size = struct.unpack("<I", f.read(4))[0]
    offset = struct.unpack("<H", f.read(2))[0]
    attr = f.read(12)
    reserved = f.read(2)


    print("-----")
    print("@ {:0X}".format(tell))
    print("magic: {}".format(magic))
    print("name: {}".format(name))
    print("ext: {}".format(ext))
    print("size: {0}({0:0X})".format(size))
    print("offset: {0}({0:0X})".format(offset))
    print("attr: {}".format(attr))
    print("resv: {}".format(reserved))

    nextoffset = offset
    while nextoffset != 0xffff:
        f.seek(0x1000 * nextoffset + AT, SEEK_SET)
        b = f.read(0x1000)
        binvalue.append(b)

        print("atvalue: ", end="")
        print(allocationTable[nextoffset])
        nextoffset = allocationTable[nextoffset]
        if nextoffset >= len(allocationTable):
            break

        
    print()

    break
o = open("kore", "wb")
for b in binvalue:
    o.write(b)

jeopardy Crypto[100] かんたんな符号化2

これはほとんど thrust2799 が解いた。

わけのわからん文字列が与えられるけど、 base64 -d すると16進値っぽい数字列になる。これを decode('hex') してやると KP から始まる文字列になるので、エンディアンをひっくり返してあげて、保存する。 file をかけると Word 2007 と出るので、 libre Office Writer で開くと 縦にフラグが書いてある。コピーして適当なファイルに保存して cat | tr -d '\n' した。

jeopardy Network[100] 寝坊気味のコンピュータ

pcap が降ってくるので strings してうまいことつなぎ合わせる

jeopardy Network[100] ログインしたいんだ!

pcap が降ってくるので strings してBase64っぽい文字列を見かけたら base64 -d する

jeopardy Web[100] ログインせよ

ログインフォームがあるのでユーザ名 admin、パスワード 'or'a'='a と入力する

jeopardy Web[100] 灯台下暗し

ログインフォームと、ログインのロジックをPHPで書いたものが表示されるので ざっと眺めて login.db みたいな名前がひっかかる。アクセスすると sqlite dbファイルが降ってくるので apt install sqlite3 して sqlite3 login.db する。 .schema で users というテーブルだけがあることがわかるので select * from usersすると admin のパスワードがフラグになっている。

jeopardy Web[200] Web1

難読化された javascript がある。適当にBeautifyして処理を一つずつ追ってやる。すると DOM を書き換えるしょりと、別のことをしている処理が存在することがわかる。 別のことをしている処理が怪しいので単体で実行するように書き換え、余計な分岐をはずしてやるとフラグが手に入る。

jeopardy Web[200] Web2

いわゆる Javascript puzzle。解き方が何処かに乗ってないかと思って調べたら burningCTF でまったく同じ問題が出題されていることがわかったのでWriteUpをみながら真似をした。

KoH 通信を解析しろ

pcap ファイルが降ってくる。サイズはそこそこ。みると、幾つかの ssh ログインとたくさんの TCP SYN がある。ssh はよくわからないIP同士で通信していることが多い。TCP SYNは 10.201.1.101から10.201.1.102 へのもの。最初はポートスキャンかなと思っていた。

ちょっとだけある HTTP パケットにフラグが書いてあり、さらにバイナリが存在することが示唆される。フラグに port knocking とあるので TCP SYN のうちACKが帰ってきたものを真似て port knocking してやると a.out が手に入る(port knocking しなくてもいいという説がある→作問者がいるって言ってた)。

フラグは thrust2799 が手に入れ、私が port knocking してみれば、と言った。 thrust2799 はバイナリ読めないと開始前から何度も言っていたので、私が解析することにしてファイルを貰った。

radare2 で見ていたがわかりにくかったので Bokken を入れてる miwpayou にも解析してもらった。どうやら a.out は 8081番で待ち受ける fork型のサーバで、fork した先でstrncmp や strlen を使い、一部で popen していることがわかった。

gdb が存在することを思い出したので set follow-fork-mode child して b do_workして、fork後の処理を追う。 strncmp では recv した文字列を SCK と比較していることがわかった。さらに次では recvした文字列+3を popenに突っ込んでいることがわかった。

そこで試しに SCKwhoami と送ってみたところ ubuntu というレスポンスが来た。 SCKls → SCKcat README → SCKecho insecure >> flag してKoHになった。

余談だけど SCKkillall a.out したらサーバからのれすぽんすがなくなった。これを運営に報告したらしばらく経って復活した。復活して SCKwhoami したら root だった。