# AIS3 pre-exam 2021 ## Info * 參賽ID: Zero871015 * 比賽日期:2021/05/22 ~ 2021/05/24 ## ReSident evil villAge [Crypto] * 本題有給程式碼,是一題關於RSA的題目。 * 題目中給予`n`和`e`,只要成功簽章`Ethan Winters`這段文字就可以得到Flag。 * 然後題目中可以幫你簽章任意內容,但不能是題目要求的`Ethan Winters`。 * 翻了一下我之前的[RSA筆記](https://hackmd.io/@Zero871015/RSA)就知道這題是考`Chosen Ciphertext Attack`。 * 詳細的攻擊理論可以參閱[這篇](https://crypto.stackexchange.com/questions/2323/how-does-a-chosen-plaintext-attack-on-rsa-work)。 ### 以下實作 * 先連到server並存下資訊。 ```python= # connect server and get n ane e r = remote('quiz.ais3.org', 42069) r.recvline() n = int(r.recvline().split(b'=')[1].strip()) e = int(r.recvline().split(b'=')[1].strip()) print('n =', n) print('e =', e) ``` * 將題目要求的名字乘上`2^e`後送出(記得要mod`n` 不然會太大),以迴避掉輸入不能是`Ethan Winters`的限制。 ```python= # name * 2^e to pass the restriction name = b'Ethan Winters' name = bytes_to_long(name) name_b = (name * pow(2, e)) % n r.sendline('1') r.recv() r.sendline(format(name_b, 'x')) ``` * 因為`2^e^d`在mod`n`的情況下會變回`2`,因此回傳的訊息除二就是答案了。 ```python= # the original result is half sig2 = int(r.recvline().split(b': ')[2].strip()) print(sig2) r.sendline('2') r.sendline(str(sig2 // 2)) r.interactive() ``` ## Cat Slayer ᶠᵃᵏᵉ | Nekogoroshi [Welcome] * Welcome的題目,主要只是要熟悉工具和環境。 * 把題目hint給的code執行後,畫面上會出現一個密碼盒。 * 可以輸入`0~9`的數字,如果正確就可以輸入下一位,否則會顯示`LOCKED`,要重新連線才能繼續。 * 因為大概只需要輸入十個數字左右,最差只要嘗試`100`次左右就可以開了,所以可以直接暴力破解。 * 可以手動也可以寫程式自動化,因為不難寫這裡就不附code了。 * 開了密碼盒之後server會丟回flag並斷開連線,如果不是用程式自動抓回傳可能會來不及看到flag畫面就消失,可以使用`python3 | tee output.txt`這種方式記錄下所以內容,事後就可以在`output.txt`看到flag了。 ## Microcheese [Misc] * 這題有給程式碼,執行一下會發現是一個遊戲程式。 ![](https://i.imgur.com/RsOtuIo.png) * 基本上就是拿石頭遊戲,和AI輪流拿,每次可以選擇一列,拿一顆以上的石頭;拿到最後一顆就贏了。 * 大家對於這種遊戲應該不陌生,都知道有必勝法了,當然用一般的方法是不可能贏的了AI的。 * 原本的解法應該是要利用遊戲中的存檔機制,去破解存檔後給的密文,自己做出一個可贏的存檔。 ![](https://i.imgur.com/CPrCJJu.png) * 不過這題code寫爛了,稍微看一下就可以發現在`play()`裡面,忘記去處理玩家選擇`0`, `1`, `2`以外的選項。 * 所以我們選擇`3`的話,就會執行上次拿石頭的動作,而如果我們上一次拿的行數已經不存在,就不會拿石頭了。 ```python= if choice == '0': pile = int(input('which pile do you choose? ')) count = int(input('how many stones do you remove? ')) if not game.make_move(pile, count): print_error('that is not a valid move!') continue elif choice == '1': game_str = game.save() digest = hash.hexdigest(game_str.encode()) print('you game has been saved! here is your saved game:') print(game_str + ':' + digest) return elif choice == '2': break # no move -> player wins! if game.ended(): win = True break else: print_move('you', count, pile) game.show() ``` * 利用這點,我們就可以讓對手一直拿石頭,直到我們可以拿最後一顆的時候再出手,就可以得到flag了。 ## 🐰 Peekora 🥒 [Reverse] ![](https://i.imgur.com/wjLhGth.png) * 因為這題太油了所以決定要做這題。 * 題目給了一個pkl檔和一段提示的command,先執行看看。 ![](https://i.imgur.com/dvVygoT.png) * 執行後要求我們輸入flag,隨便亂打則是什麼都沒發生,推測這是一支驗證flag的程式。 * google了一下pkl這個副檔名之後,會發現這是python的pickle序列化檔案。 * 使用`cat`可以直接看到opcode,或是使用python的pickletools可以得到好看一點的解譯內容。 ```python= import pickletools data = open("flag_checker.pkl", "rb") data = data.read() pickletools.dis(data) ``` ``` 0: c GLOBAL '__builtin__ input' 19: ( MARK 20: S STRING 'FLAG: ' 30: t TUPLE (MARK at 19) 31: R REDUCE 32: p PUT 0 35: 0 POP 36: c GLOBAL '__builtin__ getattr' 57: p PUT 1 60: 0 POP 61: g GET 1 64: ( MARK 65: ( MARK 66: c GLOBAL '__builtin__ exit' 84: c GLOBAL '__builtin__ str' 101: l LIST (MARK at 65) 102: S STRING '__getitem__' 117: t TUPLE (MARK at 64) 118: R REDUCE 119: p PUT 2 122: 0 POP 123: g GET 2 126: ( MARK 127: g GET 1 130: ( MARK 131: g GET 0 134: S STRING 'startswith' 148: t TUPLE (MARK at 130) 149: R REDUCE 150: ( MARK 151: S STRING 'AIS3{' 160: t TUPLE (MARK at 150) 161: R REDUCE 162: t TUPLE (MARK at 126) 163: R REDUCE 164: ( MARK 165: t TUPLE (MARK at 164) 166: R REDUCE 167: g GET 2 170: ( MARK 171: g GET 1 174: ( MARK 175: g GET 0 178: S STRING 'endswith' 190: t TUPLE (MARK at 174) 191: R REDUCE 192: ( MARK 193: S STRING '}' 198: t TUPLE (MARK at 192) 199: R REDUCE 200: t TUPLE (MARK at 170) 201: R REDUCE 202: ( MARK 203: t TUPLE (MARK at 202) 204: R REDUCE 205: g GET 2 208: ( MARK 209: g GET 1 212: ( MARK 213: g GET 1 216: ( MARK 217: g GET 0 220: S STRING '__getitem__' 235: t TUPLE (MARK at 216) 236: R REDUCE 237: ( MARK 238: I INT 6 241: t TUPLE (MARK at 237) 242: R REDUCE 243: S STRING '__eq__' 253: t TUPLE (MARK at 212) 254: R REDUCE 255: ( MARK 256: V UNICODE 'A' 259: t TUPLE (MARK at 255) 260: R REDUCE 261: t TUPLE (MARK at 208) 262: R REDUCE 263: ( MARK 264: t TUPLE (MARK at 263) 265: R REDUCE 266: g GET 2 269: ( MARK 270: g GET 1 273: ( MARK 274: g GET 1 277: ( MARK 278: g GET 0 281: S STRING '__getitem__' 296: t TUPLE (MARK at 277) 297: R REDUCE 298: ( MARK 299: I INT 9 302: t TUPLE (MARK at 298) 303: R REDUCE 304: S STRING '__eq__' 314: t TUPLE (MARK at 273) 315: R REDUCE 316: ( MARK 317: V UNICODE 'j' 320: t TUPLE (MARK at 316) 321: R REDUCE 322: t TUPLE (MARK at 269) 323: R REDUCE 324: ( MARK 325: t TUPLE (MARK at 324) 326: R REDUCE 327: g GET 1 330: ( MARK 331: g GET 0 334: S STRING '__getitem__' 349: t TUPLE (MARK at 330) 350: R REDUCE 351: ( MARK 352: I INT 9 355: t TUPLE (MARK at 351) 356: R REDUCE 357: p PUT 3 360: 0 POP 361: g GET 2 364: ( MARK 365: g GET 1 368: ( MARK 369: g GET 1 372: ( MARK 373: g GET 0 376: S STRING '__getitem__' 391: t TUPLE (MARK at 372) 392: R REDUCE 393: ( MARK 394: I INT 11 398: t TUPLE (MARK at 393) 399: R REDUCE 400: S STRING '__eq__' 410: t TUPLE (MARK at 368) 411: R REDUCE 412: ( MARK 413: V UNICODE 'p' 416: t TUPLE (MARK at 412) 417: R REDUCE 418: t TUPLE (MARK at 364) 419: R REDUCE 420: ( MARK 421: t TUPLE (MARK at 420) 422: R REDUCE 423: g GET 2 426: ( MARK 427: g GET 1 430: ( MARK 431: g GET 1 434: ( MARK 435: g GET 0 438: S STRING '__getitem__' 453: t TUPLE (MARK at 434) 454: R REDUCE 455: ( MARK 456: I INT 14 460: t TUPLE (MARK at 455) 461: R REDUCE 462: S STRING '__eq__' 472: t TUPLE (MARK at 430) 473: R REDUCE 474: ( MARK 475: g GET 3 478: t TUPLE (MARK at 474) 479: R REDUCE 480: t TUPLE (MARK at 426) 481: R REDUCE 482: ( MARK 483: t TUPLE (MARK at 482) 484: R REDUCE 485: g GET 1 488: ( MARK 489: g GET 0 492: S STRING '__getitem__' 507: t TUPLE (MARK at 488) 508: R REDUCE 509: ( MARK 510: I INT 1 513: t TUPLE (MARK at 509) 514: R REDUCE 515: p PUT 4 518: 0 POP 519: g GET 2 522: ( MARK 523: g GET 1 526: ( MARK 527: g GET 1 530: ( MARK 531: g GET 0 534: S STRING '__getitem__' 549: t TUPLE (MARK at 530) 550: R REDUCE 551: ( MARK 552: I INT 5 555: t TUPLE (MARK at 551) 556: R REDUCE 557: S STRING '__eq__' 567: t TUPLE (MARK at 526) 568: R REDUCE 569: ( MARK 570: V UNICODE 'd' 573: t TUPLE (MARK at 569) 574: R REDUCE 575: t TUPLE (MARK at 522) 576: R REDUCE 577: ( MARK 578: t TUPLE (MARK at 577) 579: R REDUCE 580: g GET 2 583: ( MARK 584: g GET 1 587: ( MARK 588: g GET 1 591: ( MARK 592: g GET 0 595: S STRING '__getitem__' 610: t TUPLE (MARK at 591) 611: R REDUCE 612: ( MARK 613: I INT 10 617: t TUPLE (MARK at 612) 618: R REDUCE 619: S STRING '__eq__' 629: t TUPLE (MARK at 587) 630: R REDUCE 631: ( MARK 632: V UNICODE 'z' 635: t TUPLE (MARK at 631) 636: R REDUCE 637: t TUPLE (MARK at 583) 638: R REDUCE 639: ( MARK 640: t TUPLE (MARK at 639) 641: R REDUCE 642: g GET 2 645: ( MARK 646: g GET 1 649: ( MARK 650: g GET 1 653: ( MARK 654: g GET 0 657: S STRING '__getitem__' 672: t TUPLE (MARK at 653) 673: R REDUCE 674: ( MARK 675: I INT 12 679: t TUPLE (MARK at 674) 680: R REDUCE 681: S STRING '__eq__' 691: t TUPLE (MARK at 649) 692: R REDUCE 693: ( MARK 694: V UNICODE 'h' 697: t TUPLE (MARK at 693) 698: R REDUCE 699: t TUPLE (MARK at 645) 700: R REDUCE 701: ( MARK 702: t TUPLE (MARK at 701) 703: R REDUCE 704: g GET 2 707: ( MARK 708: g GET 1 711: ( MARK 712: g GET 4 715: S STRING '__eq__' 725: t TUPLE (MARK at 711) 726: R REDUCE 727: ( MARK 728: g GET 1 731: ( MARK 732: g GET 0 735: S STRING '__getitem__' 750: t TUPLE (MARK at 731) 751: R REDUCE 752: ( MARK 753: I INT 13 757: t TUPLE (MARK at 752) 758: R REDUCE 759: t TUPLE (MARK at 727) 760: R REDUCE 761: t TUPLE (MARK at 707) 762: R REDUCE 763: ( MARK 764: t TUPLE (MARK at 763) 765: R REDUCE 766: g GET 2 769: ( MARK 770: g GET 1 773: ( MARK 774: g GET 1 777: ( MARK 778: g GET 0 781: S STRING '__getitem__' 796: t TUPLE (MARK at 777) 797: R REDUCE 798: ( MARK 799: I INT 8 802: t TUPLE (MARK at 798) 803: R REDUCE 804: S STRING '__eq__' 814: t TUPLE (MARK at 773) 815: R REDUCE 816: ( MARK 817: V UNICODE 'w' 820: t TUPLE (MARK at 816) 821: R REDUCE 822: t TUPLE (MARK at 769) 823: R REDUCE 824: ( MARK 825: t TUPLE (MARK at 824) 826: R REDUCE 827: g GET 2 830: ( MARK 831: g GET 1 834: ( MARK 835: g GET 1 838: ( MARK 839: g GET 0 842: S STRING '__getitem__' 857: t TUPLE (MARK at 838) 858: R REDUCE 859: ( MARK 860: I INT 7 863: t TUPLE (MARK at 859) 864: R REDUCE 865: S STRING '__eq__' 875: t TUPLE (MARK at 834) 876: R REDUCE 877: ( MARK 878: V UNICODE 'm' 881: t TUPLE (MARK at 877) 882: R REDUCE 883: t TUPLE (MARK at 830) 884: R REDUCE 885: ( MARK 886: t TUPLE (MARK at 885) 887: R REDUCE 888: c GLOBAL '__builtin__ print' 907: ( MARK 908: S STRING 'Correct!' 920: t TUPLE (MARK at 907) 921: R REDUCE 922: . STOP highest protocol among opcodes = 0 ``` * 接著就是慢慢讀了,只要知道基本的運作概念其實不難,因為裡面用到的功能很少。 * 基本上就是用stack和memo記錄所有東西。 * 大致上翻譯一下: * 126~201: 必須是`AIS3{`開頭、`}`結尾 * 238~256: `input[6] == 'A'` * 299~317: `input[9] == 'j'` * 352~357 + 456~475: `input[9] == input[14]` * 394~413: `input[11] == 'p'` * 510~515 + 712~753: `input[1] == input[13]` * 552~570: `input[5] == 'd'` * 613~632: `input[10] == 'z'` * 675~694: `input[12] == 'h'` * 799~817: `input[8] == 'w'` * 860~878: `input[7] == 'm'` * 統整起來得到`AIS3{dAmwjzphIj}`。 ## ⲩⲉⲧ ⲁⲛⲟⲧⲏⲉꞅ 𝓵ⲟ𝓰ⲓⲛ ⲣⲁ𝓰ⲉ [Web] * 總之有個登入頁面和原始碼頁面。 ```! from flask import Flask, request, make_response, redirect, session, render_template, send_file import os import json app = Flask(__name__) app.secret_key = os.urandom(32) FLAG = os.environ.get('FLAG', 'AIS3{TEST_FLAG}') users_db = { 'guest': 'guest', 'admin': os.environ.get('PASSWORD', 'S3CR3T_P455W0RD') } @app.route("/") def index(): def valid_user(user): return users_db.get(user['username']) == user['password'] if 'user_data' not in session: return render_template("login.html", message="Login Please :D") user = json.loads(session['user_data']) if valid_user(user): if user['showflag'] == True and user['username'] != 'guest': return FLAG else: return render_template("welcome.html", username=user['username']) return render_template("login.html", message="Verify Failed :(") @app.route("/login", methods=['POST']) def login(): data = '{"showflag": false, "username": "%s", "password": "%s"}' % ( request.form["username"], request.form['password'] ) session['user_data'] = data return redirect("/") @app.route("/logout") def logout(): session.clear() return redirect("/") @app.route("/sauce") def sauce(): return send_file(__file__, mimetype="text/plain") if __name__ == '__main__': app.run(threaded=True, debug=True) ``` * 原始碼頁面可以知道: 1. 有兩組帳密{guest: guest, admin: 不明} 2. 在登入時,會把輸入的username和password做驗證,然後把帳密和`showflag = false`壓成session存起來。 3. 如果存在session、`username!='guest'`、`showflag == true`就會給你flag。 * 可以發現`login()`的地方可以做注入攻擊,只要把`password`裡面加上雙引號就會壞掉。 * 做個簡單的payload測試一下能不能複寫。 * username輸入`123` * password輸入`guest","password":"guest","username":"guest` * 會發現可以成功登入guest的頁面,代表注入成功。 * ![](https://i.imgur.com/G2Qzi7M.png) * 這樣就可以解決`showflag`的部分,只需要延長上面的payload就可以了:`guest","password":"guest","showflag":true,"username":"guest` * 接下來要解決`username!='guest'`的部分,我們仔細看看驗證的部分: * `users_db.get(user['username']) == user['password']` * 如果`username`根本沒get到東西,`password`會怎麼樣呢...應該會變成空的。 * 因此我們只要username隨便打,password送`null`進去就好了! * 最後payload: * `guest","password":null,"showflag":true,"username":"peko` * ![](https://i.imgur.com/2YrHAHx.png) ## COLORS [Reverse] ![](https://i.imgur.com/v22to92.png) * 進來這個頁面可以說幾乎啥都沒有,因此我們點開F12開始挖東西。 * 挖一挖,挖到一份很可疑的js檔案,因為這題是逆向所以被搞得有點亂。 ```javascript=! const _0x3eb4 = ['repeat', '1YqKovX', 'NDBCMjBnMzBpNTFKNjA2MDFcMzB3NDAxMzBBNDFqNDBcNDExMzBnNzB1MzBpMTBrMzBsNDA3NjB4NTBpNTBYMTBLMTBJNDBoNTBYMDBLNDFpNTFsNzA2NzBmNDBvMTA2NTA1NzBLMTFuNTE4NzA3NDFCNTAtMTE4NDB3MzFhMTByNDF6NzBLMzA9MjA9MTA9', 'substr', 'output', 'getElementsByTagName', '65022JgPEZp', 'keydown', 'length', 'innerHTML', '677PRUQAU', 'ArrowLeft', 'QWxTM3tCYXNFNjRfaTUrYjByTkluZ35cUXdvLy14SDhXekNqN3ZGRDJleVZrdHFPTDFHaEtZdWZtWmRKcFg5fQ==', '133781JKLWBV', 'ArrowUp', '90407czXCgh', 'PGRpdiBzdHlsZT0id2lkdGg6IDM1MHB4OyBwb3NpdGlvbjogYWJzb2x1dGU7IGJvdHRvbTogMHB4OyBsZWZ0OiAwcHg7Ij48ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBjZW50ZXI7IGFuaW1hdGlvbjogcmFpbmJvdyAycyBsaW5lYXIgMHMgaW5maW5pdGUgbm9ybWFsOyBwb3NpdGlvbjogYWJzb2x1dGU7IHRvcDogLTEwcHg7IGxlZnQ6IDUwJTsgZm9udC1zaXplOiAyMHB4OyB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7IHdpZHRoOiAzNTBweDsiPkhlcmUgaXMgeW91cjxicj4iZW5jb2RlZCIgZmxhZyw8YnI+aW5wdXQgdG8gZW5jb2RlIHNvbWV0aGluZyBlbHNlITwvZGl2PiA8c3ZnIGlkPSLwn5CIIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEyOCAxMjgiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGlkPSJib2R5Ij48YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJmaWxsIiBkdXI9IjUwMG1zIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIga2V5VGltZXM9IjA7MC4xOzAuMjswLjM7MC40OzAuNTswLjY7MC43OzAuODswLjk7MSIgdmFsdWVzPSIgI2ZmOGQ4YjsgI2ZlZDY4OTsgIzg4ZmY4OTsgIzg3ZmZmZjsgIzhiYjVmZTsgI2Q3OGNmZjsgI2ZmOGNmZjsgI2ZmNjhmNzsgI2ZlNmNiNzsgI2ZmNjk2ODsgI2ZmOGQ4YiAiPjwvYW5pbWF0ZT48YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJkIiBkdXI9IjUwMG1zIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIga2V5VGltZXM9IjA7MC4xOzAuMjswLjM7MC40OzAuNTswLjY7MC43OzAuODswLjk7MSIgdmFsdWVzPSIgTTY3LjEsMTA5LjVjLTkuNiwwLTIzLjYtOC44LTIzLjYtMjRjMC0xMi4xLDE3LjgtNDEsMzcuNS00MWMxNS42LDAsMjMuMywxMC42LDI1LjEsMjAuNSBjMS45LDEwLjcsMy45LDguMiwzLjksMTkuNWMwLDguMi0zLjgsMTctMy44LDIyLjNjMCwzLjksMS40LDcuNCwyLjksMTAuNWMxLjcsMy41LDIuNCw2LjYsMi40LDkuMkg5LjVjMC0xMy43LDEwLjgtMTQsMjEuMS0yMyBjNS42LTQuOSwxMS0xMi40LDE0LjUtMjY7IE01Ni4xLDEwNy41Yy05LjYsMC0yNC42LTEzLjgtMjQuNi0yOWMwLTE2LjIsMTMtNDIsMzMuNS00MmMxNy44LDAsMjIuMywxMS42LDI2LjEsMjIuNSBjMy42LDEwLjMsOS45LDkuMiw5LjksMjAuNWMwLDguMi0xLjgsNy0xLjgsMTIuM2MwLDQuNSwzLjQsOC4yLDYuNCwxNC4xYzIuNSw0LjgsNC44LDExLjIsNC44LDIwLjZoLTk5YzAtMTIuMSw3LjItMTcuNiwxNC43LTI0LjMgYzMuMi0yLjksNi41LTUuOSw5LjItOS44OyBNNDUuMSwxMDkuNWMtNS41LTAuMi0yNy42LTguNC0yNy42LTI3YzAtMTcuOSwxNC44LTQyLDMyLjUtNDJjMTUuNCwwLDI0LDEwLjQsMjYuMSwyMS41IGMxLjMsNi43LDkuOSw5LjgsOS45LDIxLjVjMCw4LjItMC44LDYtMC44LDExLjNjMCw3LjcsMTIuOCw5LDIwLjgsMTVjNy43LDUuOCwxNS41LDE2LjcsMTUuNSwxNi43aC0xMTBjMC00LjgsMS43LTExLjMsNS0xNiBjMy4yLTQuNSw0LjUtOC4zLDUtMTU7IE0zNiwxMjBjLTUuNS0wLjItMjguNS0xMS45LTI4LjUtMzAuNWMwLTE2LjIsMTIuNS00MiwzMy00MmMxNy44LDAsMjEuOCw5LjYsMjUuNiwyMC41IEM2OS43LDc4LjMsNzYsNzguMiw3Niw4OS41YzAsOC4yLTAuOCw0LTAuOCw5LjNjMCw1LjksMTYuNSw3LjgsMjguNCwxNS45YzgsNS41LDE3LjksMTEuOCwxNy45LDExLjhoLTExMGMwLTIuMS0xLjItNS4yLTEuOS0xNC41IGMtMC4zLTMuNi0wLjUtOC4xLTAuNS0xMy45OyBNMzcsMTE5LjVjLTE1LDAuMS0zMy41LTEyLjctMzMuNS0zMEMzLjUsNzMuMywxNiw0NywzNi41LDQ3YzE3LjgsMCwyMi44LDExLDI2LDIyIEM2NS42LDc5LjQsNzMsNzkuMiw3Myw5MC41YzAsNC0xLjgsNi42LTEuOCw4LjNjMCw1LjksMTQuMiw2LjQsMjYuNCwxNS45YzcuNyw2LDEzLjksMTEuOCwxMy45LDExLjhINy41Yy0xLjItMy40LTEuOC03LjMtMS45LTExLjIgYy0wLjItNS4xLDAuMy0xMC4xLDAuOS0xMy43OyBNNDAuNSwxMjEuNWMtMTIuNiwwLTMwLTEzLjQtMzAtMjlDMTAuNSw3Ni4zLDIzLDUzLDQzLjUsNTNjMTQuNSwwLDIyLjgsOS42LDI1LDIyIGMxLjIsNi45LDEwLDkuMiwxMCwyMC41YzAsNCwwLDUuNiwwLDcuM2MwLDQuOSw2LjEsNy41LDExLjIsMTEuOWM1LjgsNSw3LjIsMTEuOCw3LjIsMTEuOEg4LjVjMC0xLjUtMC42LTYuMSwwLjQtMTEuOCBjMC42LTMuNSwxLjktNy41LDQuMy0xMS42OyBNNDguNSwxMjEuNWMtMTIuNiwwLTI1LTYuMy0yNS0xOGMwLTE2LjIsMTMuNy00NywzNi00N2MxNS42LDAsMjQuOCw5LjEsMjcsMjEuNSBjMS4yLDYuOSw3LDkuMiw3LDE4LjVjMCw5LjUtNCwxMS00LDIyYzAsNC4xLDAuNSw1LDEsNmMwLjUsMS4yLDEsMiwxLDJoLTgxYzAtNS4zLDMuMS04LjMsNi4zLTExLjVjMi42LTIuNiw1LjQtNS4zLDYuNy05LjU7IE02OC41LDEyMS41Yy0xMi42LDAtMzMtNS44LTMzLTIzYzAtOS4yLDExLjgtMzYsMzctMzZjMTUuNiwwLDI1LjgsOC4xLDI4LDIwLjUgYzEuMiw2LjksNCw2LjIsNCwxNS41YzAsOS41LTUsMTUuMS01LDIxYzAsMS45LDEsMi4zLDEsNWMwLDEuMiwwLDIsMCwyaC05MWMwLjUtNy42LDcuMS0xMS4xLDEzLjctMTUuN2M0LjktMy40LDkuOS03LjUsMTIuMy0xNC4zOyBNNzMuNSwxMTcuNWMtMTIuNiwwLTMwLTYuMi0zMC0yNWMwLTE0LjIsMjAuOS0zNywzOC0zN2MxNy42LDAsMjUuOCwxMS4xLDI4LDIzLjUgYzEuMiw2LjksMyw3LjIsMywxNi41YzAsMTIuMS02LDE2LjEtNiwyMmMwLDQuMiwyLDUuMywyLDhjMCwxLjIsMCwxLDAsMUg3LjVjMi4xLTkuNCwxMC40LTEzLjMsMTkuMi0xOS40IGM3LjEtNSwxNC40LTExLjUsMTguOC0yMy42OyBNODAuNSwxMTUuNWMtMTIuNiwwLTMyLTkuMi0zMi0yOGMwLTE0LjIsMjIuOS0zNSw0MC0zNWMxNy42LDAsMjUuOCwxMi4xLDI4LDI0LjUgYzEuMiw2LjksMyw2LjIsMywxNS41YzAsMTIuMS02LDE5LjEtNiwyNWMwLDQuMiwyLDUuMywyLDhjMCwxLjIsMCwxLDAsMWgtMTAyYzIuMy04LjcsMTEuNi0xMS43LDIwLjgtMjAuMSBjNS4zLTQuOCwxMC41LTExLjQsMTQuMi0yMS45OyBNNjcuMSwxMDkuNWMtOS42LDAtMjMuNi04LjgtMjMuNi0yNGMwLTEyLjEsMTcuOC00MSwzNy41LTQxYzE1LjYsMCwyMy4zLDEwLjYsMjUuMSwyMC41IGMxLjksMTAuNywzLjksOC4yLDMuOSwxOS41YzAsOC4yLTMuOCwxNy0zLjgsMjIuM2MwLDMuOSwxLjQsNy40LDIuOSwxMC41YzEuNywzLjUsMi40LDYuNiwyLjQsOS4ySDkuNWMwLTEzLjcsMTAuOC0xNCwyMS4xLTIzIGM1LjYtNC45LDExLTEyLjQsMTQuNS0yNiAiPjwvYW5pbWF0ZT48L3BhdGg+PHBhdGggaWQ9ImJlYWsiIGZpbGw9IiM3YjhjNjgiPjxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImQiIGR1cj0iNTAwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBrZXlUaW1lcz0iMDswLjE7MC4yOzAuMzswLjQ7MC41OzAuNjswLjc7MC44OzAuOTsxIiB2YWx1ZXM9IiBNNzguMjksNzBjMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzc4LjI5LDg1LjUsNzguMjksNzBaOyBNNjIuMjksNjRjMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzYyLjI5LDc5LjUsNjIuMjksNjRaOyBNNDguMjksNjdjMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzQ4LjI5LDgyLjUsNDguMjksNjdaOyBNMzYuMjksNzNjMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzM2LjI5LDg4LjUsMzYuMjksNzNaOyBNMzUuMjksNzVjMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzM1LjI5LDkwLjUsMzUuMjksNzVaOyBNNDEuMjksODFjMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzQxLjI5LDk2LjUsNDEuMjksODFaOyBNNTkuMjksODRjMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzU5LjI5LDk5LjUsNTkuMjksODRaOyBNNzIuMjksODljMC05LjkyLDIuNS0xNCw4LTE0czgsMi4xNyw4LDEwLjY3YzAsMTUuOTItNywyNi4zMy03LDI2LjMzUzcyLjI5LDEwNC41LDcyLjI5LDg5WjsgTTgwLjI5LDgyYzAtOS45MiwyLjUtMTQsOC0xNHM4LDIuMTcsOCwxMC42N2MwLDE1LjkyLTcsMjYuMzMtNywyNi4zM1M4MC4yOSw5Ny41LDgwLjI5LDgyWjsgTTg3LjI5LDc4YzAtOS45MiwyLjUtMTQsOC0xNHM4LDIuMTcsOCwxMC42N2MwLDE1LjkyLTcsMjYuMzMtNywyNi4zM1M4Ny4yOSw5My41LDg3LjI5LDc4WjsgTTc4LjI5LDcwYzAtOS45MiwyLjUtMTQsOC0xNHM4LDIuMTcsOCwxMC42N2MwLDE1LjkyLTcsMjYuMzMtNywyNi4zM1M3OC4yOSw4NS41LDc4LjI5LDcwWiAiPjwvYW5pbWF0ZT48L3BhdGg+PGVsbGlwc2UgaWQ9ImV5ZS1yaWdodCIgcng9IjMiIHJ5PSI0Ij48YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJjeCIgZHVyPSI1MDBtcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGtleVRpbWVzPSIwOzAuMTswLjI7MC4zOzAuNDswLjU7MC42OzAuNzswLjg7MC45OzEiIHZhbHVlcz0iMTAwOzg0OzcwOzU4OzU3OzYzOzgxOzk0OzEwMjsxMDk7MTAwIj48L2FuaW1hdGU+PGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3kiIGR1cj0iNTAwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBrZXlUaW1lcz0iMDswLjE7MC4yOzAuMzswLjQ7MC41OzAuNjswLjc7MC44OzAuOTsxIiB2YWx1ZXM9IjYyOzU2OzU5OzY1OzY3OzczOzc2OzgxOzc0OzcwOzYyIj48L2FuaW1hdGU+PC9lbGxpcHNlPjxlbGxpcHNlIGlkPSJleWUtbGVmdCIgcng9IjMiIHJ5PSI0Ij48YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJjeCIgZHVyPSI1MDBtcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGtleVRpbWVzPSIwOzAuMTswLjI7MC4zOzAuNDswLjU7MC42OzAuNzswLjg7MC45OzEiIHZhbHVlcz0iNjcuNTs1MS41OzM3LjU7MjUuNTsyNC41OzMwLjU7NDguNTs2MS41OzY5LjU7NzYuNTs2Ny41Ij48L2FuaW1hdGU+PGFuaW1hdGUgYXR0cmlidXRlTmFtZT0iY3kiIGR1cj0iNTAwbXMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBrZXlUaW1lcz0iMDswLjE7MC4yOzAuMzswLjQ7MC41OzAuNjswLjc7MC44OzAuOTsxIiB2YWx1ZXM9IjYyOzU2OzU5OzY1OzY3OzczOzc2OzgxOzc0OzcwOzYyIj48L2FuaW1hdGU+PC9lbGxpcHNlPjwvc3ZnPjwvZGl2Pg==', '131837PcDnWL', '19pQimXL', '623605MIswVM', 'charCodeAt', 'join', '4WsUYDr', '686oWrfyq', 'body', 'map', 'getElementById', 'textContent', 'match', 'key', '302349wKdZHP', '4OYJFlQ', 'input', 'padStart', 'Backspace']; function _0x4ebd(_0x532d69, _0x212ed3) { _0x532d69 = _0x532d69 - 0x1c6; let _0x3eb4a7 = _0x3eb4[_0x532d69]; return _0x3eb4a7; } (function(_0x496f79, _0x226742) { const _0x463eac = _0x4ebd; while (!![]) { try { const _0x12c745 = -parseInt(_0x463eac(0x1e6)) + parseInt(_0x463eac(0x1ce)) * -parseInt(_0x463eac(0x1de)) + -parseInt(_0x463eac(0x1db)) + -parseInt(_0x463eac(0x1e7)) * parseInt(_0x463eac(0x1d7)) + -parseInt(_0x463eac(0x1d5)) * parseInt(_0x463eac(0x1c9)) + parseInt(_0x463eac(0x1df)) * -parseInt(_0x463eac(0x1d2)) + parseInt(_0x463eac(0x1d9)) * parseInt(_0x463eac(0x1da)); if (_0x12c745 === _0x226742) break; else _0x496f79['push'](_0x496f79['shift']()); } catch (_0xe36f7) { _0x496f79['push'](_0x496f79['shift']()); } } }(_0x3eb4, 0x57a76), (()=>{ const _0x1cd51f = _0x4ebd , _0x54579e = _0x1cd51f(0x1d8) , _0x78ed5a = _0x1cd51f(0x1ca) , _0x24fcac = _0x1cd51f(0x1d4) , _0x2a3765 = 0x8 , _0x317b6e = 0xa; let _0x1e21d9, _0x179193 = 0x0; function _0xce93(_0x1b497a) { const _0x9fe181 = _0x1cd51f; if (!_0x1b497a[_0x9fe181(0x1d0)]) return ''; let _0x4d62de = '' , _0x23f867 = '' , _0x5395cb = 0x0; for (let _0x6e40b4 = 0x0; _0x6e40b4 < _0x1b497a[_0x9fe181(0x1d0)]; _0x6e40b4++) _0x4d62de += _0x1b497a[_0x9fe181(0x1dc)](_0x6e40b4)['toString'](0x2)[_0x9fe181(0x1c6)](0x8, '0'); _0x5395cb = _0x4d62de[_0x9fe181(0x1d0)] % _0x317b6e / 0x2 - 0x1; if (_0x5395cb != -0x1) _0x4d62de += '0'[_0x9fe181(0x1c8)](_0x317b6e - _0x4d62de[_0x9fe181(0x1d0)] % _0x317b6e); _0x4d62de = _0x4d62de[_0x9fe181(0x1e4)](/(.{1,10})/g); for (let _0x13c6bb of _0x4d62de) { let _0x192141 = parseInt(_0x13c6bb, 0x2); _0x23f867 += _0x9f530c(_0x192141 >> 0x6 & 0x7, _0x192141 >> 0x9, atob(_0x24fcac)[_0x192141 & 0x3f]); } for (; _0x5395cb > 0x0; _0x5395cb--) { _0x23f867 += _0x9f530c(_0x5395cb % _0x2a3765, 0x0, '='); } return _0x23f867; } let _0x9f530c = (_0xcc032b,_0x2a8089,_0x244c3a)=>'<span><div\x20class=\x22c' + _0xcc032b + '\x20r' + _0x2a8089 + '\x22>' + _0x244c3a + '</div></span>' , _0x1fdafa = _0x29e3ab=>document[_0x1cd51f(0x1e2)](_0x1cd51f(0x1cc))['innerHTML'] = _0xce93(_0x29e3ab); document['addEventListener'](_0x1cd51f(0x1cf), _0x23e75c=>{ const _0x12b963 = _0x1cd51f; if (_0x23e75c[_0x12b963(0x1e5)] === _0x12b963(0x1c7) && _0x179193 == 0xa) _0x1e21d9[_0x12b963(0x1e3)] = _0x1e21d9['textContent'][_0x12b963(0x1cb)](0x0, _0x1e21d9['textContent'][_0x12b963(0x1d0)] - 0x1); else { if (_0x23e75c['key'] === _0x12b963(0x1d6) && !(_0x179193 >> 0x1)) return _0x179193 += 0x1; else { if (_0x23e75c[_0x12b963(0x1e5)] === 'ArrowDown' && !(_0x179193 >> 0x2)) return _0x179193 += 0x1; else { if (_0x23e75c[_0x12b963(0x1e5)] === _0x12b963(0x1d3) && (_0x179193 == 0x4 || _0x179193 == 0x6)) return _0x179193 += 0x1; else { if (_0x23e75c[_0x12b963(0x1e5)] === 'ArrowRight' && (_0x179193 == 0x5 || _0x179193 == 0x7)) return _0x179193 += 0x1; else { if (_0x23e75c[_0x12b963(0x1e5)] === 'b' && _0x179193 == 0x8) return _0x179193 += 0x1; else { if (_0x23e75c['key'] === 'a' && _0x179193 == 0x9) return document[_0x12b963(0x1cd)](_0x12b963(0x1e0))[0x0]['innerHTML'] += atob(_0x54579e), _0x1e21d9 = document[_0x12b963(0x1e2)](_0x12b963(0x1e8)), _0x1e21d9[_0x12b963(0x1d1)] = '', document[_0x12b963(0x1e2)]('output')[_0x12b963(0x1d1)] = atob(_0x78ed5a)[_0x12b963(0x1e4)](/(.{1,3})/g)[_0x12b963(0x1e1)](_0x5efa9e=>_0x9f530c(_0x5efa9e[0x0], _0x5efa9e[0x1], _0x5efa9e[0x2]))[_0x12b963(0x1dd)](''), _0x179193 += 0x1; else { if (_0x23e75c[_0x12b963(0x1e5)][_0x12b963(0x1d0)] == 0x1 && _0x179193 == 0xa) _0x1e21d9[_0x12b963(0x1e3)] += String['fromCharCode'](_0x23e75c[_0x12b963(0x1e5)][_0x12b963(0x1dc)]()); else return; } } } } } } } _0x1fdafa(_0x1e21d9[_0x12b963(0x1e3)]); } ); } )()); ``` * 這邊我稍微通靈了一下,看到檔案裏面有`ArrowDown`、`ArrowRight`、`b`和`a`,又叫我們輸入SecretCode,就想到要打`上上下下左右左右ba`,頁面就變成一個加密器。 * ![](https://i.imgur.com/xJVpBeW.png) * 畫面上一開始的文字是Flag加密成為的結果,然後我們可以輸入隨意文字,他會吐出密文。 * ![](https://i.imgur.com/erlAUL7.png) * 知道用途是加密之後就可以開始做逆向了,先找出哪一段是加密,然後看懂加密邏輯。 * 可以發現密文後面有很多`=`,估計是padding用的,因此推測加密應該是以下這段: ```javascript=! function _0xce93(_0x1b497a) { const _0x9fe181 = _0x1cd51f; if (!_0x1b497a[_0x9fe181(0x1d0)]) return ''; let _0x4d62de = '' , _0x23f867 = '' , _0x5395cb = 0x0; for (let _0x6e40b4 = 0x0; _0x6e40b4 < _0x1b497a[_0x9fe181(0x1d0)]; _0x6e40b4++) _0x4d62de += _0x1b497a[_0x9fe181(0x1dc)](_0x6e40b4)['toString'](0x2)[_0x9fe181(0x1c6)](0x8, '0'); _0x5395cb = _0x4d62de[_0x9fe181(0x1d0)] % _0x317b6e / 0x2 - 0x1; if (_0x5395cb != -0x1) _0x4d62de += '0'[_0x9fe181(0x1c8)](_0x317b6e - _0x4d62de[_0x9fe181(0x1d0)] % _0x317b6e); _0x4d62de = _0x4d62de[_0x9fe181(0x1e4)](/(.{1,10})/g); for (let _0x13c6bb of _0x4d62de) { let _0x192141 = parseInt(_0x13c6bb, 0x2); _0x23f867 += _0x9f530c(_0x192141 >> 0x6 & 0x7, _0x192141 >> 0x9, atob(_0x24fcac)[_0x192141 & 0x3f]); } for (; _0x5395cb > 0x0; _0x5395cb--) { _0x23f867 += _0x9f530c(_0x5395cb % _0x2a3765, 0x0, '='); } return _0x23f867; } ``` * 經過我翻譯過後大概變成這樣: ```javascript=! const _0x1cd51f = _0x4ebd , _0x54579e = _0x1cd51f(0x1d8) , _0x78ed5a = _0x1cd51f(0x1ca) , _0x24fcac = _0x1cd51f(0x1d4) , 8 = 0x8 , 10 = 0xa; let _0x1e21d9, _0x179193 = 0x0; function _0xce93(input) { const _0x9fe181 = _0x1cd51f; if (!input[len]) return ''; let bits = '' , html = '' , cb = 0x0; for (let i = 0x0; i < input[len]; i++) bits += input[charCodeAt](i)['toString'](0x2)[padStart](0x8, '0'); cb = bits[len] % 10 / 0x2 - 0x1; if (cb != -0x1) bits += '0'[repeat](10 - bits[len] % 10); bits = bits[match](/(.{1,10})/g); //每10個切一段 for (let bit of bits) { let num = parseInt(bit, 0x2); html += htmlFunction(num >> 0x6 & 0x7, num >> 0x9, "AlS3{BasE64_i5+b0rNIng~\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}"[num & 0x3f]); } for (; cb > 0x0; cb--) { html += htmlFunction(cb % _0x2a3765, 0, '='); } return html; } ``` * 看code的一些方法: * chrome可以按F12下中斷點,然後隨便輸入一個字讓它執行加密就會暫停。 * 可以逐步執行之外,某些看不懂的字串代換直接反白會直接幫你翻譯。 * ![](https://i.imgur.com/881VKeS.png) * 加密流程大概是: 1. 每個字元用`charCodeAt`轉成二進位數字,不滿八位元的就在前面補`0`。 2. 全部二進位數字串起來,改為每十個切一組,不滿十個的在後面補`0`。 3. 每組數字前四個bit變成顏色的資訊,後六個bit根據密碼表作代換。 * 基本上沒有任何地方是不可逆的加密,所以就寫一個解密器,把加密流程反過來就好了: ```python=! code = "BgiJ6\\w1Aj\\1guikl7xiXKIhXKil6fo65Kn87B-8warzK" table = "AlS3{BasE64_i5+b0rNIng~\\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}" bits = "" # get color information temp = '<div id="output"><span><div class="c4 r0">B</div></span><span><div class="c2 r0">g</div></span><span><div class="c3 r0">i</div></span><span><div class="c5 r1">J</div></span><span><div class="c6 r0">6</div></span><span><div class="c0 r1">\</div></span><span><div class="c3 r0">w</div></span><span><div class="c4 r0">1</div></span><span><div class="c3 r0">A</div></span><span><div class="c4 r1">j</div></span><span><div class="c4 r0">\</div></span><span><div class="c4 r1">1</div></span><span><div class="c3 r0">g</div></span><span><div class="c7 r0">u</div></span><span><div class="c3 r0">i</div></span><span><div class="c1 r0">k</div></span><span><div class="c3 r0">l</div></span><span><div class="c4 r0">7</div></span><span><div class="c6 r0">x</div></span><span><div class="c5 r0">i</div></span><span><div class="c5 r0">X</div></span><span><div class="c1 r0">K</div></span><span><div class="c1 r0">I</div></span><span><div class="c4 r0">h</div></span><span><div class="c5 r0">X</div></span><span><div class="c0 r0">K</div></span><span><div class="c4 r1">i</div></span><span><div class="c5 r1">l</div></span><span><div class="c7 r0">6</div></span><span><div class="c7 r0">f</div></span><span><div class="c4 r0">o</div></span><span><div class="c1 r0">6</div></span><span><div class="c5 r0">5</div></span><span><div class="c7 r0">K</div></span><span><div class="c1 r1">n</div></span><span><div class="c5 r1">8</div></span><span><div class="c7 r0">7</div></span><span><div class="c4 r1">B</div></span><span><div class="c5 r0">-</div></span><span><div class="c1 r1">8</div></span><span><div class="c4 r0">w</div></span><span><div class="c3 r1">a</div></span><span><div class="c1 r0">r</div></span><span><div class="c4 r1">z</div></span><span><div class="c7 r0">K</div></span><span><div class="c3 r0">=</div></span><span><div class="c2 r0">=</div></span><span><div class="c1 r0">=</div></span></div>' temp = temp.split("class")[1:] xxx = [int(t[3]) for t in temp] x = [int(t[6]) for t in temp] # reverse to original code for i, c in enumerate(code): num = table.find(c) bits += f"{x[i]:01b}{xxx[i]:03b}{num:06b}" # 10 bits to 8 bits chunks, chunk_size = len(bits), 8 byte = [ bits[i:i+chunk_size] for i in range(0, chunks, chunk_size) ] # output flag for b in byte: print(chr(int(b, 2)), end="") ``` ``` AIS3{base1024_15_c0l0RFuL_GAM3_CL3Ar_thIS_IS_y0Ur_FlaG!} ``` * 成功得到flag。 ###### tags: `CTF`