# KCSC RECRUITMENT CTF 2025 ## Just_EzReversing ![image](https://hackmd.io/_uploads/SJOV0D2Mbg.png) ### 1. Test chương trình Trước hết, em chạy challenge này và thử xem output như thế nào ![image](https://hackmd.io/_uploads/r17ci72zWx.png) chương trình có chuỗi `Enter the flag:`, tiến hành vào IDA để dò string và tiến đến hàm main ### 2. sub_140002934() ![image](https://hackmd.io/_uploads/r1G-2Qhz-e.png) double click vào chuỗi đấy và sử dụng `Ctl + X` để đến địa chỉ hàm gọi chuỗi ```cpp= __int64 sub_140002934() { FILE *v0; // rax signed __int64 v1; // rax double v2; // xmm6_8 double v3; // xmm0_8 size_t v4; // rax _QWORD *v5; // rcx __int64 v6; // rdx char *Str; // [rsp+20h] [rbp-10h] int i; // [rsp+2Ch] [rbp-4h] sub_14000E1F7(); sub_140018900("%s", aEnterTheFlag); v0 = __acrt_iob_func(0); fgets(Buffer, 100, v0); Buffer[strcspn(Buffer, "\n")] = 0; v1 = strlen(Buffer); if ( v1 < 0 ) { v3 = (double)(int)(v1 & 1 | ((unsigned __int64)v1 >> 1)); v2 = v3 + v3; } else { v2 = (double)(int)v1; } if ( v2 == sub_140019F60(6, 2) ) { v4 = strlen(Buffer); Str = (char *)sub_1400027A8(Buffer, v4, 1); for ( i = 0; i < strlen(Str); i += 2 ) { strncpy(Destination, &Str[i], 2u); sub_140002753(Destination, &qword_140026120); v5 = (_QWORD *)((char *)&unk_1400261A0 + dword_1400263A0); v6 = qword_140026128; *v5 = qword_140026120; v5[1] = v6; dword_1400263A0 += 16; } if ( !memcmp(&unk_1400261A0, &unk_14001B040, 0x200u) ) { sub_140018900("Correct!\n"); return 1; } else { sub_140018900("Incorrect!\n"); return 0; } } else { sub_140018900("Incorrect length!\n"); return 0; } } ``` Chuỗi em nhập vào được lưu vào biến `buffer` và biến `v2` có nhiệm vụ kiểm tra độ dài input, `sub_140019F60(6, 2)` return `pow(6,2) = 36` ```cpp if ( v2 == sub_140019F60(6, 2) ) { v4 = strlen(Buffer); Str = (char *)sub_1400027A8(Buffer, v4, 1); for ( i = 0; i < strlen(Str); i += 2 ) { strncpy(Destination, &Str[i], 2u); sub_140002753(Destination, &qword_140026120); v5 = (_QWORD *)((char *)&unk_1400261A0 + dword_1400263A0); v6 = qword_140026128; *v5 = qword_140026120; v5[1] = v6; dword_1400263A0 += 16; } if ( !memcmp(&unk_1400261A0, &unk_14001B040, 0x200u) ) { sub_140018900("Correct!\n"); return 1; } else { sub_140018900("Incorrect!\n"); return 0; } } else { sub_140018900("Incorrect length!\n"); return 0; } ``` hàm `sub_1400027A8(Buffer, v4, 1)` có thể là hàm biến đổi chuỗi chứa thuật toán, và chuỗi đã biến đổi sẽ được lưu vào biến `Str`, trong đoạn vòng lặp for, chương trình sẽ duyệt `Str` từng 2 ký tự một và lấy cặp ký tự đó xử lý với hàm `sub_140002753()`, tổng cộng 16 byte kết quả sẽ được lưu vào `unk_1400261A0`. Sau đó lấy kết quả so sánh với `unk_14001B040`, nếu đúng thì thông báo Correct!, nếu không thì Incorrect!. Xem dữ liệu bên trong `unk_14001B040` có 512 byte, qua đó tiến hành dump truy suất dữ liệu. ### 3. sub_1400027A8() ```cpp= __int64 __fastcall sub_1400027A8(__int64 a1, unsigned __int64 a2, char a3) { __int64 v3; // rax __int64 v4; // rax __int64 v5; // rax __int64 v7; // [rsp+28h] [rbp-28h] unsigned __int64 i; // [rsp+30h] [rbp-20h] int v9; // [rsp+38h] [rbp-18h] unsigned int v10; // [rsp+3Ch] [rbp-14h] __int64 v11; // [rsp+40h] [rbp-10h] unsigned __int64 v12; // [rsp+48h] [rbp-8h] v12 = 8 * ((a2 + 4) / 5); if ( a3 != 1 ) v12 = (8 * a2 + 4) / 5; v7 = sub_140019FC0(v12 + 1); v11 = 0; v10 = 0; v9 = 0; for ( i = 0; i < a2; ++i ) { v10 = (v10 << 8) | *(unsigned __int8 *)(a1 + i); for ( v9 += 8; v9 > 4; v9 -= 5 ) { v3 = v11++; *(_BYTE *)(v7 + v3) = aAbcdefghijklmn[(v10 >> (v9 - 5)) & 0x1F]; } } if ( v9 > 0 ) { v4 = v11++; *(_BYTE *)(v7 + v4) = aAbcdefghijklmn[(v10 << (5 - v9)) & 0x1F]; } if ( a3 ) { while ( (v11 & 7) != 0 ) { v5 = v11++; *(_BYTE *)(v7 + v5) = 61; } } *(_BYTE *)(v7 + v11) = 0; return v7; } ``` Sau khi dump được data, phân tích thêm hàm `sub_1400027A8` dựa vào chuỗi `aAbcdefghijklmn` và check trong `.rdata:000000014001C060 aAbcdefghijklmn db 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',0` nhận ra rằng đây là mapping của base32 encoding ```cpp for ( i = 0; i < a2; ++i ) { v10 = (v10 << 8) | *(unsigned __int8 *)(a1 + i); for ( v9 += 8; v9 > 4; v9 -= 5 ) { v3 = v11++; *(_BYTE *)(v7 + v3) = aAbcdefghijklmn[(v10 >> (v9 - 5)) & 31]; } } if ( v9 > 0 ) { v4 = v11++; *(_BYTE *)(v7 + v4) = aAbcdefghijklmn[(v10 << (5 - v9)) & 0x1F]; } ``` Cơ chế vòng lặp này thực hiện 8 bit dữ liệu đầu vào của biến `v10` sẽ được dịch trái, biến `v9` có nhiệm vụ theo dõi số lượng bit. Cắt lấy 5 bit cao nhất, chuyển thành index để lấy ký tự trong map. Và cuối cùng giảm biến đếm `v9` đi 5 để chuẩn bị cho lần đếm tiếp theo. ```cpp if ( a3 ) { while ( (v11 & 7) != 0 ) { v5 = v11++; *(_BYTE *)(v7 + v5) = 61; } } *(_BYTE *)(v7 + v11) = 0; return v7; ``` Đoạn code cuối hàm thực hiện thêm padding để chuỗi đạt độ chuẩn ### 4. sub_140002753() ```cpp= __int64 __fastcall sub_140002753(const char *a1, __int64 a2) { size_t v2; // rax _BYTE v4[96]; // [rsp+20h] [rbp-60h] BYREF sub_140002420(v4); v2 = strlen(a1); sub_14000246E(v4, a1, v2); return sub_14000250B(v4, a2); } ``` Đây là hàm Wrapper 3 hàm con, cần tìm bên trong 3 hàm này như thế nào ### 5. sub_140002420() ```cpp= __int64 __fastcall sub_140002420(__int64 a1) { *(_DWORD *)(a1 + 64) = 0; *(_QWORD *)(a1 + 72) = 0; *(_DWORD *)(a1 + 80) = 1732584193; *(_DWORD *)(a1 + 84) = -271733879; *(_DWORD *)(a1 + 88) = -1732584194; *(_DWORD *)(a1 + 92) = 271733878; return a1; } ``` Đây là hàm khởi tạo MD5 hash với 4 vecto khởi tạo. Có thể thấy rằng khi chuyển `1732584193` sang mã hex là `0x67452301` và lấy mã hex chuyển sang little endianess thì sẽ được Word A = `01234567` và cũng áp dụng như thế với 3 hằng số còn lại. Từ các hằng số trên, ta có thể đoán chắc challenge sử dụng thuật toán chuẩn MD5 và base32. Tiến hành viết script ### 6. Script Dump mảng `unk_14001B040`: ```cpp #include <idc.idc> static main() { auto START = 0x14001B040; auto SIZE = 0x200; auto i, f; f = fopen("C:\\Users\\TYL3R\\Documents\\ctf\\event\\kcsc-recruitment\\unk_14001B040.txt", "w"); for ( i = 0; i < SIZE; i++ ) { fprintf(f, "0x%02X", Byte(START + i)); if ( i != SIZE - 1 ) fprintf(f, ", "); if ( (i + 1) % 16 == 0 ) fprintf(f, "\n"); } fclose(f); } ``` Script chính giải challenge: ```python import hashlib, base64 unk_14001B040 = [ 0xD9, 0xB2, 0x52, 0x86, 0x6A, 0x25, 0x98, 0x6F, 0xD8, 0xEC, 0x8A, 0x6C, 0x9B, 0xDE, 0xD2, 0x75, 0xC7, 0x9B, 0xDA, 0x0E, 0x66, 0xF0, 0x7B, 0xEE, 0x66, 0x2A, 0x68, 0x40, 0xB9, 0x07, 0xBF, 0x39, 0x8D, 0x36, 0xB3, 0x61, 0xA4, 0x79, 0x44, 0x22, 0xC7, 0x06, 0x81, 0x58, 0x6A, 0x95, 0x73, 0x50, 0x18, 0x2B, 0xE0, 0xC5, 0xCD, 0xCD, 0x50, 0x72, 0xBB, 0x18, 0x64, 0xCD, 0xEE, 0x4D, 0x3D, 0x6E, 0xAC, 0xCB, 0x66, 0xF0, 0xEC, 0xD8, 0x26, 0xAA, 0xC8, 0x90, 0x65, 0x99, 0x0E, 0x1D, 0xA9, 0x7F, 0x56, 0x9B, 0x3C, 0xED, 0x35, 0x55, 0xBF, 0xAE, 0x63, 0xCA, 0xDB, 0x65, 0x58, 0xA6, 0xFC, 0xBE, 0x33, 0xD2, 0xA0, 0x8E, 0xEE, 0x4C, 0x17, 0x81, 0x19, 0x40, 0xFE, 0xFB, 0xC4, 0xE0, 0x97, 0x78, 0x6B, 0xA8, 0x9A, 0xB4, 0x18, 0x23, 0xAF, 0x9B, 0x65, 0xEA, 0x5D, 0x23, 0x30, 0x31, 0xB9, 0xF8, 0xE4, 0xA5, 0x9D, 0xF8, 0xB9, 0x72, 0x06, 0x10, 0x9E, 0xB4, 0xB7, 0xF2, 0xFE, 0x52, 0x8A, 0x4D, 0xAD, 0x44, 0xAC, 0x8C, 0x02, 0x28, 0x5F, 0x6B, 0x91, 0x64, 0xA0, 0x84, 0x92, 0xA7, 0x8F, 0x92, 0x0A, 0x5B, 0x65, 0x72, 0x5F, 0x6A, 0x3B, 0xA9, 0x58, 0x21, 0xCE, 0xC6, 0x1D, 0xBB, 0x46, 0xD3, 0x2F, 0xF2, 0xD4, 0x5E, 0x98, 0x3C, 0x58, 0xE9, 0x6C, 0x9B, 0xA3, 0x3D, 0x69, 0x72, 0x1C, 0xE8, 0x51, 0xF5, 0x81, 0x93, 0x77, 0x65, 0x89, 0x0F, 0x2A, 0x70, 0x6C, 0x77, 0xEA, 0x8A, 0xF3, 0xCC, 0xA3, 0x6C, 0x71, 0xEB, 0x23, 0x9A, 0x02, 0x31, 0xBA, 0x0C, 0x6E, 0x7A, 0x45, 0x90, 0xF0, 0x62, 0x42, 0xAD, 0xC7, 0x77, 0xF3, 0xC2, 0xC9, 0x70, 0x05, 0x15, 0x0C, 0xB7, 0xB8, 0x61, 0x65, 0xB6, 0xC2, 0x3F, 0xA9, 0x99, 0x69, 0x25, 0xB6, 0x10, 0x71, 0x0D, 0x93, 0xE2, 0x8C, 0x59, 0xA3, 0xE2, 0xD5, 0xC4, 0x42, 0x58, 0xD5, 0x16, 0x59, 0xF9, 0x62, 0x79, 0xC4, 0x70, 0xCE, 0x81, 0x85, 0xDC, 0x6E, 0x02, 0x9F, 0xBF, 0xA8, 0x17, 0xDF, 0xC4, 0x58, 0x59, 0x8A, 0x5D, 0x38, 0xA6, 0x75, 0x5F, 0x7C, 0x78, 0xEB, 0xB4, 0xC2, 0x23, 0xC9, 0x6F, 0x8E, 0x6C, 0xCC, 0x29, 0xF7, 0x3C, 0xC2, 0x8E, 0x76, 0xAA, 0x96, 0x36, 0x9A, 0xBB, 0xBA, 0x52, 0xE6, 0x21, 0xBF, 0xA8, 0x3D, 0xA8, 0xE6, 0x4F, 0xBA, 0x7D, 0x08, 0x34, 0xD2, 0x3B, 0x91, 0x50, 0x4B, 0xED, 0x68, 0x59, 0x01, 0xDC, 0x3A, 0xC5, 0x38, 0xF9, 0x9A, 0xBB, 0xC1, 0xD3, 0x39, 0xC2, 0x77, 0xC0, 0x66, 0x9E, 0x7B, 0xC3, 0x73, 0xC0, 0x0A, 0x5B, 0x65, 0x72, 0x5F, 0x6A, 0x3B, 0xA9, 0x58, 0x21, 0xCE, 0xC6, 0x1D, 0xBB, 0x46, 0xD3, 0x85, 0x3B, 0xC5, 0x5C, 0x10, 0x41, 0xF2, 0x4B, 0x93, 0x92, 0x06, 0x93, 0x31, 0xE6, 0x53, 0x36, 0xF2, 0x14, 0xA7, 0xD4, 0x2E, 0x0D, 0xE5, 0x87, 0x5D, 0x55, 0x18, 0x9E, 0x01, 0xE2, 0xE1, 0x87, 0x7B, 0x7C, 0xD2, 0x4E, 0xA6, 0xF0, 0x8B, 0x71, 0x1C, 0xF4, 0x05, 0x3B, 0xEA, 0xC4, 0x3C, 0xC5, 0x64, 0xF3, 0xBD, 0x17, 0x41, 0xAB, 0x8D, 0x6B, 0xA5, 0x45, 0xA1, 0xAE, 0x09, 0xBB, 0x87, 0x28, 0x8A, 0x6B, 0x17, 0x8D, 0x3A, 0xF0, 0xA5, 0xA9, 0xB2, 0x74, 0x4C, 0xA3, 0x19, 0x21, 0xD5, 0xE2, 0xBF, 0xD2, 0x34, 0x39, 0x10, 0x37, 0x8A, 0x1B, 0x53, 0x7C, 0x3C, 0x80, 0x36, 0xA5, 0xC0, 0x7E, 0x20, 0x63, 0x84, 0x97, 0x17, 0xD3, 0x2D, 0xD1, 0x9E, 0x53, 0x4B, 0x77, 0xCA, 0xBA, 0xC5, 0x17, 0x20, 0x63, 0x84, 0x97, 0x17, 0xD3, 0x2D, 0xD1, 0x9E, 0x53, 0x4B, 0x77, 0xCA, 0xBA, 0xC5, 0x17, 0x20, 0x63, 0x84, 0x97, 0x17, 0xD3, 0x2D, 0xD1, 0x9E, 0x53, 0x4B, 0x77, 0xCA, 0xBA, 0xC5, 0x17 ] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" table = {hashlib.md5((a+b).encode()).digest(): a+b for a in alphabet for b in alphabet} base32_encoded = "" for i in range(32): chunk = bytes(unk_14001B040[i*16:(i+1)*16]) base32_encoded += table.get(chunk, "==") flag = base64.b32decode(base32_encoded) print(flag.decode()) ``` **Flag:** `KCSC{3z_bru73_md5_4nd_d3cod3_b4s332}` ## puRE ![image](https://hackmd.io/_uploads/HyYURD2G-g.png) ### main ```cpp= __int64 __fastcall main(int a1, char **a2, char **a3) { __int64 v3; // rsi char v5[256]; // [rsp+0h] [rbp-710h] BYREF char dest[512]; // [rsp+100h] [rbp-610h] BYREF char s[1008]; // [rsp+300h] [rbp-410h] BYREF __int64 v8; // [rsp+6F0h] [rbp-20h] unsigned int seed; // [rsp+6F8h] [rbp-18h] int v10; // [rsp+6FCh] [rbp-14h] char *v11; // [rsp+700h] [rbp-10h] __int64 i; // [rsp+708h] [rbp-8h] seed = time(0); srand(seed); puts("======================================================="); puts("= PUZZLE CTF CHALLENGE ="); puts("======================================================="); sub_131C("=======================================================", a2); while ( 1 ) { printf("\n>> "); if ( !fgets(s, 1000, stdin) ) break; s[strcspn(s, "\n")] = 0; if ( s[0] ) { strcpy(dest, s); for ( i = 0; dest[i]; ++i ) dest[i] = tolower((unsigned __int8)dest[i]); if ( !strcmp(dest, "quit") || !strcmp(dest, "exit") ) { puts("Bye!"); return 0; } if ( !strcmp(dest, "solution") ) { if ( dword_AFA0 ) sub_2605(); else puts("[!] Doc code chua ma go command linh tinh?"); } else if ( !strcmp(dest, "test") ) { sub_131C(dest, "test"); dword_6168 = 0; sub_2391(36); sub_2941(); } else if ( !strcmp(dest, "help") ) { printf("[!] Ga the, cho cai nay thi co du trinh solve khong: 0x%x", seed); } else if ( !strcmp(dest, "challenge") ) { if ( dword_616C ) { puts("\n[!] Starting REAL CHALLENGE..."); puts("[!] Scrambling 1836 times. Du trinh ko???\n"); sub_131C("[!] Scrambling 1836 times. Du trinh ko???\n", "challenge"); puts("[!] Reset challenge...Done\n"); dword_6168 = 1; sub_2391(1836); } else { puts("\n[!] You must complete TEST mode first!"); } } else if ( dword_AFA0 ) { v8 = 0; v11 = strtok(s, " "); v10 = 0; while ( v11 ) { if ( (unsigned int)sub_2169(v11) ) ++v10; else printf("[!] Unknown: %s\n", v11); v11 = strtok(0, " "); } if ( v10 > 0 ) { if ( (unsigned int)sub_2894() ) { putchar(10); puts("================================================"); puts("= CONGRATULATIONS! CHALLENGE SOLVED! ="); puts("================================================"); if ( dword_6168 ) { sub_1219(v5, 256); printf(" FLAG: %s\n", v5); v3 = (unsigned int)dword_6164; printf(" Total moves: %d\n", dword_6164); putchar(10); puts(" You are a TRUE Rot's Master!"); } else { putchar(10); puts(" Nice! You solved the practice cube!"); v3 = (unsigned int)dword_6164; printf(" Total moves: %d\n", dword_6164); putchar(10); puts(" >> You can now access CHALLENGE mode!"); puts(" >> Type 'challenge' for the REAL challenge!"); dword_616C = 1; } puts("================================================"); if ( dword_6168 ) return 0; sub_131C("================================================", v3); dword_6168 = 0; dword_AFA0 = 0; } else { if ( dword_6164 > 998 ) { puts("\n[!] Too many moves! This puzzle is not that kind of ez=)))"); return 0xFFFFFFFFLL; } printf("Moves: %d\n", dword_6164); } } } else { puts("[!] Doc code chua ma go tinh tinh zay"); } } } return 0; } ``` Tiến vào hàm main của chương trình, Đây là challenge giải đố rubik. Thấy rằng seed được tạo ngẫu nhiên dựa theo thời gian thực: ```cpp seed = time(0); srand(seed); ``` Dựa vào lệnh help khi chạy chương trình, ta có thể in và hiển thị seed này: ```cpp else if ( !strcmp(dest, "help") ) { printf("[!] Ga the, cho cai nay thi co du trinh solve khong: 0x%x", seed); ``` Đồng thời challenge cũng có các lệnh với từng vai trò khác nhau: 1. `quit\help`: Thoát chương trình 2. `solution`: Có thể là in lời giải, nhưng trước đó lệnh này kiểm tra `dword_616C`, phải vào test mode 3. `test`: chuyển sang chế độ chơi test và xáo trộn 36 bước đi 4. `challenge`: chuyển sang chế độ giải thật, thực hiện `sub_2391(1836)`, nhưng phải xong test mode thì mới bắt đầu challenge. Tóm lại hàm main thực hiện kiểm tra input đầu vào với các câu lệnh nhằm thực hiện các hàm khi thao tác với lệnh đó. Có 1 trở ngại nhỏ: ```cpp else { if ( dword_6164 > 998 ) { puts("\n[!] Too many moves! This puzzle is not that kind of ez=)))"); return 0xFFFFFFFFLL; } printf("Moves: %d\n", dword_6164); } ``` Challenge thực hiện xáo trộn 1836 bước nhưng giới hạn số bước đi là 998 bước. vì thế em cần giải thuật riêng để giải được challenge này. Sau khi có AI hỗ trợ thì giải thuật phù hợp giải quyết vấn đề trên là **Kociemba** ### Script ```python import sys, ctypes, ctypes.util, kociemba try: libc = ctypes.CDLL(ctypes.util.find_library('c') or 'libc.so.6') except: sys.exit("Libc not found") seed = int(sys.argv[1], 16) if len(sys.argv) > 1 else int(input("Seed: "), 16) libc.srand(seed) def solve(n): s = list("U"*9 + "R"*9 + "F"*9 + "D"*9 + "L"*9 + "B"*9) def cycle(idxs): tmp = [s[i] for i in idxs[-1]] for i in range(3, 0, -1): for k in range(3): s[idxs[i][k]] = s[idxs[i-1][k]] for k in range(3): s[idxs[0][k]] = tmp[k] def rotate(f): b = f * 9 t = s[b:b+9] s[b:b+9] = [t[6], t[3], t[0], t[7], t[4], t[1], t[8], t[5], t[2]] if f == 0: cycle([[18,19,20], [36,37,38], [45,46,47], [9,10,11]]) elif f == 1: cycle([[8,5,2], [45,48,51], [35,32,29], [26,23,20]]) elif f == 2: cycle([[6,7,8], [9,12,15], [29,28,27], [44,41,38]]) elif f == 3: cycle([[24,25,26], [15,16,17], [51,52,53], [42,43,44]]) elif f == 4: cycle([[0,3,6], [18,21,24], [27,30,33], [53,50,47]]) elif f == 5: cycle([[2,1,0], [36,39,42], [33,34,35], [17,14,11]]) faces = [0, 0, 3, 3, 4, 4, 1, 1, 2, 2, 5, 5] for _ in range(n): r = libc.rand() % 12 for _ in range(3 if r % 2 else 1): rotate(faces[r]) sol = kociemba.solve("".join(s)).split() return " ".join([m[0] + " " + m[0] if '2' in m else m for m in sol]) print(f"\n[ TEST INPUT ]:\n{solve(36)}") print(f"\n[ CHALLENGE INPUT ]:\n{solve(1836)}") ``` **Flag:** `KCSC{Does the "p" in puRE stand for puzzle or pwn? Congrats, no matter how u solve it. puREvenge in KCSC CTF won't be this easy}` ## 33,550,337 ### Phân tích ![image](https://hackmd.io/_uploads/HJCoAvhzZe.png) Challenge này cho 1 file app-realease.apk. Lần đầu tiên em phân tích file này nên có vẻ hơi lạ. Sau khi tìm kiếm một hồi lâu thì cuối cùng em sử dụng công cụ để phân tích file này chính là **Blutter**. Em tìm được link sau đây: https://github.com/worawit/blutter tiến hành giải nén file zip sau khi tải trên github thì em được folder `blutter-main` ![image](https://hackmd.io/_uploads/B1TLROnz-x.png) Giải nén file apk challenge cái đã: ![image](https://hackmd.io/_uploads/SkSIyt3fWx.png) Sau khi giải nén sẽ có các thư mục con bên trong thư mục `test`. Nhiệm vụ là đi tìm file `libapp.so` ![image](https://hackmd.io/_uploads/SJ-EWK3fWl.png) Phát hiện 3 file `libapp.so`, em tìm hiểu được blutter hỗ trợ kiến trúc **arm64-v8a**. Vì vậy em tiến hành sử dụng blutter để tạo pseudo-code với cấu trúc tên file đuôi `.dart` để có thể đọc được ![image](https://hackmd.io/_uploads/SyOMNYhMWe.png) Chạy thử lệnh thì cứ thông báo lỗi không tìm thấy file. Xem thử file `blutter.py` thì mới biết là *Blutter đang tìm file trong một **DIRECTORY**, không phải file trực tiếp* ![image](https://hackmd.io/_uploads/SkLoEK2M-g.png) Qua đó tiến hành chạy lại lệnh ![image](https://hackmd.io/_uploads/Hy7USF2fWg.png) Tìm file `main.dart`: ![image](https://hackmd.io/_uploads/HkmPLt3fZg.png) Đã thấy đường dẫn của file, tiến hành mở file phân tích Sau khi em mở file `main.dart`, đây là chương trình assembly có rất nhiều đoạn code, em thử Ctrl + F để tìm các từ khóa như **Encrypt** hay **Decrypt** thì may mắn là có kết quả thật ```asm _ decryptFlag(/* No info */) { // ** addr: 0x2ddeac, size: 0x88 // 0x2ddeac: EnterFrame // 0x2ddeac: stp fp, lr, [SP, #-0x10]! // 0x2ddeb0: mov fp, SP // 0x2ddeb4: AllocStack(0x10) // 0x2ddeb4: sub SP, SP, #0x10 // 0x2ddeb8: CheckStackOverflow // 0x2ddeb8: ldr x16, [THR, #0x38] ; THR::stack_limit // 0x2ddebc: cmp SP, x16 // 0x2ddec0: b.ls #0x2ddf2c // 0x2ddec4: r0 = Key() // 0x2ddec4: bl #0x307358 ; AllocateKeyStub -> Key (size=0xc) // 0x2ddec8: mov x1, x0 // 0x2ddecc: r2 = "..As_Tomorrow_Became_Yesterday.." // 0x2ddecc: add x2, PP, #0xb, lsl #12 ; [pp+0xb978] "..As_Tomorrow_Became_Yesterday.." // 0x2dded0: ldr x2, [x2, #0x978] // 0x2dded4: stur x0, [fp, #-8] // 0x2dded8: r0 = Encrypted.fromUtf8() // 0x2dded8: bl #0x307178 ; [package:encrypt/encrypt.dart] Encrypted::Encrypted.fromUtf8 // 0x2ddedc: r0 = IV() // 0x2ddedc: bl #0x30716c ; AllocateIVStub -> IV (size=0xc) // 0x2ddee0: mov x1, x0 // 0x2ddee4: r2 = "ThisLoveForever." // 0x2ddee4: add x2, PP, #0xb, lsl #12 ; [pp+0xb980] "ThisLoveForever." // 0x2ddee8: ldr x2, [x2, #0x980] // 0x2ddeec: stur x0, [fp, #-0x10] // 0x2ddef0: r0 = Encrypted.fromUtf8() // 0x2ddef0: bl #0x307178 ; [package:encrypt/encrypt.dart] Encrypted::Encrypted.fromUtf8 // 0x2ddef4: r0 = AES() // 0x2ddef4: bl #0x307160 ; AllocateAESStub -> AES (size=0x1c) // 0x2ddef8: mov x1, x0 // 0x2ddefc: ldur x2, [fp, #-8] // 0x2ddf00: stur x0, [fp, #-8] // 0x2ddf04: r0 = AES() // 0x2ddf04: bl #0x2de254 ; [package:encrypt/encrypt.dart] AES::AES // 0x2ddf08: r0 = Encrypter() // 0x2ddf08: bl #0x2de248 ; AllocateEncrypterStub -> Encrypter (size=0xc) // 0x2ddf0c: mov x1, x0 // 0x2ddf10: ldur x0, [fp, #-8] // 0x2ddf14: StoreField: r1->field_7 = r0 // 0x2ddf14: stur w0, [x1, #7] // 0x2ddf18: ldur x2, [fp, #-0x10] // 0x2ddf1c: r0 = decrypt64() // 0x2ddf1c: bl #0x2ddf34 ; [package:encrypt/encrypt.dart] Encrypter::decrypt64 // 0x2ddf20: LeaveFrame // 0x2ddf20: mov SP, fp // 0x2ddf24: ldp fp, lr, [SP], #0x10 // 0x2ddf28: ret // 0x2ddf28: ret // 0x2ddf2c: r0 = StackOverflowSharedWithoutFPURegs() // 0x2ddf2c: bl #0x41ba0c ; StackOverflowSharedWithoutFPURegsStub // 0x2ddf30: b #0x2ddec4 ``` Nhận ra đây là thuật toán mã hóa AES ```asm // 0x2ddec4: r0 = Key() // 0x2ddec4: bl #0x307358 ; AllocateKeyStub -> Key (size=0xc) // 0x2ddec8: mov x1, x0 // 0x2ddecc: r2 = "..As_Tomorrow_Became_Yesterday.." // 0x2ddecc: add x2, PP, #0xb, lsl #12 ; [pp+0xb978] "..As_Tomorrow_Became_Yesterday.." // 0x2dded0: ldr x2, [x2, #0x978] // 0x2dded4: stur x0, [fp, #-8] ``` Thu thập được **AES Key 32 byte**: `Key = "..As_Tomorrow_Became_Yesterday.."` ```asm // 0x2dded8: r0 = Encrypted.fromUtf8() // 0x2dded8: bl #0x307178 ; [package:encrypt/encrypt.dart] Encrypted::Encrypted.fromUtf8 // 0x2ddedc: r0 = IV() // 0x2ddedc: bl #0x30716c ; AllocateIVStub -> IV (size=0xc) // 0x2ddee0: mov x1, x0 // 0x2ddee4: r2 = "ThisLoveForever." // 0x2ddee4: add x2, PP, #0xb, lsl #12 ; [pp+0xb980] "ThisLoveForever." // 0x2ddee8: ldr x2, [x2, #0x980] // 0x2ddeec: stur x0, [fp, #-0x10] ``` Đoạn code trên ta lại thu thập được **Vector khởi tạo (IV)**: `IV = "ThisLoveForever."` Vấn đề là em không thất đoạn plaintext encrypt đâu, vì thế hơi khó khăn cho việc giải challenge. Em thử dò trong tất cả các hàm 1 lúc thì đã phát hiện ra: ```asm _ _FinalChapterScreenState(/* No info */) { // ** addr: 0x3464e8, size: 0xa8 // 0x3464e8: EnterFrame // 0x3464e8: stp fp, lr, [SP, #-0x10]! // 0x3464ec: mov fp, SP // 0x3464f0: AllocStack(0x10) // 0x3464f0: sub SP, SP, #0x10 // 0x3464f4: r5 = Sentinel // 0x3464f4: ldr x5, [PP, #0x40] ; [pp+0x40] Sentinel // 0x3464f8: r4 = "..As_Tomorrow_Became_Yesterday.." // 0x3464f8: add x4, PP, #0xb, lsl #12 ; [pp+0xb978] "..As_Tomorrow_Became_Yesterday.." // 0x3464fc: ldr x4, [x4, #0x978] // 0x346500: r3 = "ThisLoveForever." // 0x346500: add x3, PP, #0xb, lsl #12 ; [pp+0xb980] "ThisLoveForever." // 0x346504: ldr x3, [x3, #0x980] // 0x346508: r2 = "2B2BWXU9AKGAI3dHVxf7Lvk48Vcu9rQ0YUaI7f/mtC2f0VmRSDTYuYfVsKIif052czU9ER0RaVjENzA1oGglla+yiWpVbmZZSSfB8xypggPU93q1PKp9Nu+bWjYMuDJ4+6gJ9TEHLg8yJi2nnrhWgd+TmCWA+70qFhMyzVmMH9nhFB0y5Aoi8pyp0W4x/aj5RX3xsXZgt/kVPIoy+nc2Tw==" // 0x346508: add x2, PP, #0xb, lsl #12 ; [pp+0xb988] "2B2BWXU9AKGAI3dHVxf7Lvk48Vcu9rQ0YUaI7f/mtC2f0VmRSDTYuYfVsKIif052czU9ER0RaVjENzA1oGglla+yiWpVbmZZSSfB8xypggPU93q1PKp9Nu+bWjYMuDJ4+6gJ9TEHLg8yJi2nnrhWgd+TmCWA+70qFhMyzVmMH9nhFB0y5Aoi8pyp0W4x/aj5RX3xsXZgt/kVPIoy+nc2Tw==" // 0x34650c: ldr x2, [x2, #0x988] // 0x346510: r0 = 33550337 // 0x346510: movz x0, #0xf001 // 0x346514: movk x0, #0x1ff, lsl #16 // 0x346518: stur x1, [fp, #-8] // 0x34651c: CheckStackOverflow // 0x34651c: ldr x16, [THR, #0x38] ; THR::stack_limit // 0x346520: cmp SP, x16 // 0x346524: b.ls #0x346588 // 0x346528: StoreField: r1->field_1b = r5 // 0x346528: stur w5, [x1, #0x1b] // 0x34652c: StoreField: r1->field_1f = r5 // 0x34652c: stur w5, [x1, #0x1f] // 0x346530: StoreField: r1->field_27 = r0 // 0x346530: stur x0, [x1, #0x27] // 0x346534: StoreField: r1->field_2f = rZR // 0x346534: stur xzr, [x1, #0x2f] // 0x346538: StoreField: r1->field_37 = r4 // 0x346538: stur w4, [x1, #0x37] // 0x34653c: StoreField: r1->field_3b = r3 // 0x34653c: stur w3, [x1, #0x3b] // 0x346540: StoreField: r1->field_3f = r2 // 0x346540: stur w2, [x1, #0x3f] // 0x346544: r0 = AudioPlayer() // 0x346544: bl #0x34a74c ; AllocateAudioPlayerStub -> AudioPlayer (size=0x40) // 0x346548: mov x1, x0 // 0x34654c: stur x0, [fp, #-0x10] // 0x346550: r0 = AudioPlayer() // 0x346550: bl #0x346590 ; [package:audioplayers/src/audioplayer.dart] AudioPlayer::AudioPlayer // 0x346554: ldur x0, [fp, #-0x10] // 0x346558: ldur x1, [fp, #-8] // 0x34655c: StoreField: r1->field_23 = r0 // 0x34655c: stur w0, [x1, #0x23] // 0x346560: ldurb w16, [x1, #-1] // 0x346564: ldurb w17, [x0, #-1] // 0x346568: and x16, x17, x16, lsr #2 // 0x34656c: tst x16, HEAP, lsr #32 // 0x346570: b.eq #0x346578 // 0x346574: bl #0x419ff4 ; WriteBarrierWrappersStub // 0x346578: r0 = Null // 0x346578: mov x0, NULL // 0x34657c: LeaveFrame // 0x34657c: mov SP, fp // 0x346580: ldp fp, lr, [SP], #0x10 // 0x346584: ret // 0x346584: ret // 0x346588: r0 = StackOverflowSharedWithoutFPURegs() // 0x346588: bl #0x41ba0c ; StackOverflowSharedWithoutFPURegsStub // 0x34658c: b #0x346528 } ``` Một đoạn base64 đã xuất hiện trong hàm này. Qua đó em tiến hành sử dụng tools để giải challenge này ### Solution ![image](https://hackmd.io/_uploads/r1ugRK2zWx.png) **Flag:** `KCSC{now stay true thaw winter's rime anew woven memories your boon and like a melody sing our reverie and in our memory pen the end of our journey}` ## The reward lies hidden ![image](https://hackmd.io/_uploads/r1x4L52zbl.png) ### main() Sau những đoạn code khai báo biến: ```cpp= v3 = 0; byte_14000D0B4[0] ^= 0x12u; byte_14000D0B5 ^= 0x3Du; byte_14000D0B6 ^= 0x46u; byte_14000D0B7 ^= 0xA1u; byte_14000D0B8 ^= 0x42u; byte_14000D0B9 ^= 0xDFu; *(_OWORD *)Block = 0; v48.m128i_i64[0] = 0; v4 = 15; v48.m128i_i64[1] = 15; LOBYTE(Block[0]) = 0; v44 = 0; *(_QWORD *)&v5 = -1; do *(_QWORD *)&v5 = v5 + 1; while ( byte_14000D0B4[v5] ); *((_QWORD *)&v5 + 1) = 0x7FFFFFFFFFFFFFFFLL; if ( (unsigned __int64)v5 > 0x7FFFFFFFFFFFFFFFLL ) sub_140001220(argc, argv, envp); if ( (unsigned __int64)v5 <= 15 ) { v45.m128i_i64[0] = v5; v45.m128i_i64[1] = 15; memcpy(&v44, byte_14000D0B4, v5); *((_BYTE *)&v44 + v5) = 0; goto LABEL_19; } if ( ((unsigned __int64)v5 | 0xF) > 0x7FFFFFFFFFFFFFFFLL ) { v6 = 0x8000000000000027uLL; LABEL_8: v7 = operator new(v6); if ( !v7 ) goto LABEL_22; v8 = (_QWORD *)(((unsigned __int64)v7 + 39) & 0xFFFFFFFFFFFFFFE0uLL); *(v8 - 1) = v7; goto LABEL_18; } *((_QWORD *)&v5 + 1) = v5 | 0xF; if ( ((unsigned __int64)v5 | 0xF) < 0x16 ) *((_QWORD *)&v5 + 1) = 22; v9 = *((_QWORD *)&v5 + 1) + 1LL; if ( *((_QWORD *)&v5 + 1) == -1 ) { v8 = 0; } else { if ( v9 >= 0x1000 ) { v6 = *((_QWORD *)&v5 + 1) + 40LL; if ( *((_QWORD *)&v5 + 1) + 40LL < (unsigned __int64)(*((_QWORD *)&v5 + 1) + 1LL) ) sub_140001180(v9, argv, envp); goto LABEL_8; } v8 = operator new(v9); } LABEL_18: *(_QWORD *)&v44 = v8; v45 = (__m128i)v5; memcpy(v8, byte_14000D0B4, v5); *((_BYTE *)v8 + v5) = 0; v4 = v48.m128i_u64[1]; LABEL_19: if ( v4 > 0xF ) { v10 = Block[0]; if ( v4 + 1 >= 0x1000 ) { v10 = (void *)*((_QWORD *)Block[0] - 1); if ( (unsigned __int64)((char *)Block[0] - (char *)v10 - 8) > 0x1F ) ``` Đoạn code phía trên thực hiện XOR 6 byte với từng giá trị khác nhau. Có thể là chuỗi hiển thị ra màn hình nhằm tránh dò strings. Sau khi thử Xor thì xác nhận các byte trên là ký tự in ra màn hình. Ở các Label `LABEL 8:`, `LABEL 18:` và `LABEL 19:` thì đoạn code trên thực hiện việc xử lý cấp phát động và giải phóng bộ nhớ, có thể không liên quan đến thuật toán, em tạm thời bỏ qua. Ở `LABEL 22: ` đây có thể là hàm thuật toán thực sự: ```cpp LABEL_22: invalid_parameter_noinfo_noreturn(); } j_j_free(v10); } *(_OWORD *)Block = v44; v48 = v45; v11 = Block; if ( _mm_srli_si128(v45, 8).m128i_u64[0] > 0xF ) v11 = (void **)v44; v12 = sub_140002880(std::cout, v11, v45.m128i_i64[0]); std::ostream::operator<<(v12, sub_140002260); sub_1400022A0(std::cin, Block); if ( (v48.m128i_i64[0] ^ 0x7A) == 0xC2 ) { v13 = 0; v14 = v48.m128i_u64[1]; v15 = (void **)Block[0]; do { v16 = Block; if ( v14 > 0xF ) v16 = v15; v17 = SBYTE6(v16[v13]); v18 = Block; if ( v14 > 0xF ) v18 = v15; v19 = ((__int64)SBYTE3(v18[v13]) << 8) | v17; v20 = Block; if ( v14 > 0xF ) v20 = v15; v21 = ((__int64)SBYTE1(v20[v13]) << 16) | v19; v22 = Block; if ( v14 > 0xF ) v22 = v15; v23 = ((__int64)SLOBYTE(v22[v13]) << 24) | v21; v24 = Block; if ( v14 > 0xF ) v24 = v15; v25 = ((__int64)SBYTE4(v24[v13]) << 32) | v23; v26 = Block; if ( v14 > 0xF ) v26 = v15; v27 = ((__int64)SBYTE5(v26[v13]) << 40) | v25; v28 = Block; if ( v14 > 0xF ) v28 = v15; v29 = ((__int64)SBYTE2(v28[v13]) << 48) | v27; v30 = Block; if ( v14 > 0xF ) v30 = v15; v46[v13] = (void *)(v29 | ((__int64)SHIBYTE(v30[v13]) << 56)); ++v13; } while ( v13 < 23 ); for ( i = 0; i < 23; ++i ) { v32 = sub_140001A10(*(_QWORD *)&byte_140004430[i * 8 + 64], *(_QWORD *)((char *)&unk_14000AB00 + i * 8), v14, v15); v35 = sub_140001A10(v32, *(_QWORD *)&byte_140004430[i * 8 + 512], v33, v34); v46[i] = (void *)sub_140001A10(v35, v46[i], v36, v37); } for ( j = 0; j < 184; j += 8 ) { v39 = v46[(unsigned __int64)j / 8]; v40 = ((((((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 8) | ((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 4) | ((((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 8) | ((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32); if ( (((unsigned int)(v40 >> 2) | (unsigned int)v40 | (unsigned int)(((v40 >> 2) | v40) >> 1)) & 1) != 0 ) { sub_140001020("\n"); do { sub_1400026B0(std::cout, (unsigned __int8)(byte_140004430[v3 + 488] ^ byte_140004430[v3 + 40])); ++v3; } while ( v3 < 14 ); goto LABEL_59; } } sub_140001020("\n"); v41 = (char *)&unk_1400046F0; do { sub_140001020("%c"); if ( (unsigned int)v3 % 0x61 == 96 ) sub_140001020("\n"); LODWORD(v3) = v3 + 1; v41 += 4; } while ( (int)v3 < 6402 ); } else { do { sub_1400026B0(std::cout, (unsigned __int8)(byte_140004430[v3 + 440] ^ byte_140004430[v3])); ++v3; } while ( v3 < 34 ); } ``` Từ các dòng đầu tiên, đoạn code thực hiện việc nhận input và kiểm tra độ dài input đó. Dựa vào `if ( (v48.m128i_i64[0] ^ 0x7A) == 0xC2 )`, `v48.m128i_i64[0]` chính là độ dài của chuỗi nhập vào, thực hiện giải ngược với `v48.m128i_i64[0] = 0xC2 ^ 0x7A = 184`, tức là **Flag** bắt buộc phải dài 184 ký tự. Nếu quá 184 ký tự thì nhảy xuống else và in thông báo lỗi. Chuỗi nhập vào được lưu vào `Block` ```cpp do { v16 = Block; if ( v14 > 0xF ) v16 = v15; v17 = SBYTE6(v16[v13]); v18 = Block; if ( v14 > 0xF ) v18 = v15; v19 = ((__int64)SBYTE3(v18[v13]) << 8) | v17; v20 = Block; if ( v14 > 0xF ) v20 = v15; v21 = ((__int64)SBYTE1(v20[v13]) << 16) | v19; v22 = Block; if ( v14 > 0xF ) v22 = v15; v23 = ((__int64)SLOBYTE(v22[v13]) << 24) | v21; v24 = Block; if ( v14 > 0xF ) v24 = v15; v25 = ((__int64)SBYTE4(v24[v13]) << 32) | v23; v26 = Block; if ( v14 > 0xF ) v26 = v15; v27 = ((__int64)SBYTE5(v26[v13]) << 40) | v25; v28 = Block; if ( v14 > 0xF ) v28 = v15; v29 = ((__int64)SBYTE2(v28[v13]) << 48) | v27; v30 = Block; if ( v14 > 0xF ) v30 = v15; v46[v13] = (void *)(v29 | ((__int64)SHIBYTE(v30[v13]) << 56)); ++v13; } while ( v13 < 23 ); ``` Em thấy rằng chương trình chia input thành 23 khối (8 byte mỗi khối) và tiến hành đảo lộn vị trí của các ký tự bên trong mỗi khối. Qua các phép dịch bit `<<` và toán tử `OR`, thứ tự các byte gốc tức [0, 1, 2, 3, 4, 5, 6, 7] bị đảo lộn thành [6, 3, 1, 0, 4, 5, 2, 7] ```cpp for ( i = 0; i < 23; ++i ) { v32 = sub_140001A10(*(_QWORD *)&byte_140004430[i * 8 + 64], *(_QWORD *)((char *)&unk_14000AB00 + i * 8), v14, v15); v35 = sub_140001A10(v32, *(_QWORD *)&byte_140004430[i * 8 + 512], v33, v34); v46[i] = (void *)sub_140001A10(v35, v46[i], v36, v37); } for ( j = 0; j < 184; j += 8 ) { v39 = v46[(unsigned __int64)j / 8]; v40 = ((((((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 8) | ((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 4) | ((((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 8) | ((((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32)) >> 16) | ((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) | (((unsigned __int64)v39 - *(_QWORD *)&byte_140004430[j + 256]) >> 32); if ( (((unsigned int)(v40 >> 2) | (unsigned int)v40 | (unsigned int)(((v40 >> 2) | v40) >> 1)) & 1) != 0 ) { sub_140001020("\n"); do { sub_1400026B0(std::cout, (unsigned __int8)(byte_140004430[v3 + 488] ^ byte_140004430[v3 + 40])); ++v3; } while ( v3 < 14 ); goto LABEL_59; } } ``` ```cpp for ( i = 0; i < 23; ++i ) { v32 = sub_140001A10(*(_QWORD *)&byte_140004430[i * 8 + 64], *(_QWORD *)((char *)&unk_14000AB00 + i * 8), v14, v15); v35 = sub_140001A10(v32, *(_QWORD *)&byte_140004430[i * 8 + 512], v33, v34); v46[i] = (void *)sub_140001A10(v35, v46[i], v36, v37); } ``` Vòng lặp trên thực hiện việc chạy qua 23 khối input. Bên trong vòng lặp for gọi 3 lần hàm `sub_140001A10`, 2 lần đầu tại `v32` và `v35` có mảng `byte_140004430` (tại offset 64 và 512) và` unk_14000AB00`, lần 3 thì kết hợp lại kết quả của các bước trên với input v46[i] đồng thời thực hiện ghi đè. Các phép dịch bitwise cho dù nhiều nhưng thực chất đây chỉ là kiểm tra điều kiện, nếu điều kiện sai thì sẽ in ra thông báo lỗi. vì thiết không cần bận tâm đến đoạn code đó. Ở đoạn code cuối cùng chỉ là dọn dẹp dữ liệu tránh rò rỉ bộ nhớ. Hàm tiếp theo em phân tích chính là hàm `sub_140001A10` được gọi qua vòng lặp for để xem nó là gì ### sub_140001A10() ```cpp= __int64 __fastcall sub_140001A10(unsigned __int64 a1, unsigned __int64 a2) { __int64 v4; // r14 unsigned __int64 v5; // r8 unsigned __int64 v6; // rbx unsigned __int64 v7; // r8 unsigned __int64 v8; // rdi unsigned int v9; // eax char v10; // r8 char v11; // cl unsigned __int64 v12; // r8 unsigned int v13; // edx char v14; // r9 char v15; // cl unsigned int v16; // edx unsigned __int64 v17; // r8 unsigned __int64 v18; // rax char v19; // cl v4 = 0; while ( 1 ) { v5 = ((((((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2)) >> 8) | ((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2)) >> 4) | ((((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2)) >> 8) | ((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2); if ( !(v5 & 1 | ((v5 & 4) != 0) | (((v5 | (v5 >> 2)) & 2) != 0)) ) break; if ( sub_140001710(a2, 1) ) { v6 = a1; while ( 1 ) { v7 = ((((((v6 | HIDWORD(v6)) >> 16) | v6 | HIDWORD(v6)) >> 8) | ((v6 | HIDWORD(v6)) >> 16) | v6 | HIDWORD(v6)) >> 4) | ((((v6 | HIDWORD(v6)) >> 16) | v6 | HIDWORD(v6)) >> 8) | ((v6 | HIDWORD(v6)) >> 16) | v6 | HIDWORD(v6); if ( !(v7 & 1 | ((v7 & 4) != 0) | (((v7 | (v7 >> 2)) & 2) != 0)) ) break; v8 = sub_140001710(v4, v6); v4 = sub_140001510(v4, v6); v6 = 0; v9 = 1; do { if ( v9 >= 0x40 ) break; v10 = v9 + 1; v11 = v9; v6 |= ((v8 >> ((unsigned __int8)v9 - 1)) & 1) << v9; if ( v9 + 1 >= 0x40 ) break; v9 += 2; v6 |= ((v8 >> v11) & 1) << v10; } while ( v9 - 1 < 0x40 ); } } v12 = 0; v13 = 1; do { if ( v13 >= 0x40 ) break; v14 = v13 + 1; v15 = v13; v12 |= ((a1 >> ((unsigned __int8)v13 - 1)) & 1) << v13; if ( v13 + 1 >= 0x40 ) break; v13 += 2; v12 |= ((a1 >> v15) & 1) << v14; } while ( v13 - 1 < 0x40 ); a1 = v12; v16 = 1; v17 = 0; do { if ( v16 >= 0x40 ) break; v17 |= ((a2 >> v16) & 1) << ((unsigned __int8)v16 - 1); if ( v16 + 1 >= 0x40 ) break; v18 = a2 >> ((unsigned __int8)v16 + 1); v19 = v16; v16 += 2; v17 |= (v18 & 1) << v19; } while ( v16 - 1 < 0x40 ); a2 = v17; } return v4; } ``` Hàm nhận vào 2 tham số `a1` và `a2` phân tích ở đoạn code đầu tiên: ```cpp v4 = 0; while ( 1 ) { v5 = ((((((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2)) >> 8) | ((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2)) >> 4) | ((((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2)) >> 8) | ((a2 | HIDWORD(a2)) >> 16) | a2 | HIDWORD(a2); if ( !(v5 & 1 | ((v5 & 4) != 0) | (((v5 | (v5 >> 2)) & 2) != 0)) ) break; ``` Có rất nhiều phép `OR` các phần của tham số a2 lại với nhau, `OR` chỉ trả về 0 khi tất cả các bit thành phần đều là 0. Vì thế code này thực chất chỉ để kiểm tra xem có bit nào của `a2` bằng 1 không, tương tự với điều kiện `if (a2 == 0) break;` vòng lặp chạy tới khi a2 = 0, tương tự với v7. ```cpp v8 = sub_140001710(v4, v6); v4 = sub_140001510(v4, v6); v6 = 0; v9 = 1; do { if ( v9 >= 0x40 ) break; v10 = v9 + 1; v11 = v9; v6 |= ((v8 >> ((unsigned __int8)v9 - 1)) & 1) << v9; if ( v9 + 1 >= 0x40 ) break; v9 += 2; v6 |= ((v8 >> v11) & 1) << v10; } while ( v9 - 1 < 0x40 ); ``` phân tích đoạn code trên thì thực hiện việc cộng số được viết bằng các phép bitwise. Hàm `sub_140001710` đóng vai trò tính phần **nhớ (Carry - AND)**, hàm `sub_140001510` tính phần **tổng (Sum - XOR)**, và vòng lặp `do...while` thực hiện thao tác dịch trái phần **nhớ (Carry << 1)**. Quy trình lặp lại cho đến khi phần **nhớ** bằng 0 ```cpp v12 = 0; v13 = 1; do { if ( v13 >= 0x40 ) break; v14 = v13 + 1; v15 = v13; v12 |= ((a1 >> ((unsigned __int8)v13 - 1)) & 1) << v13; if ( v13 + 1 >= 0x40 ) break; v13 += 2; v12 |= ((a1 >> v15) & 1) << v14; } while ( v13 - 1 < 0x40 ); a1 = v12; v16 = 1; v17 = 0; do { if ( v16 >= 0x40 ) break; v17 |= ((a2 >> v16) & 1) << ((unsigned __int8)v16 - 1); if ( v16 + 1 >= 0x40 ) break; v18 = a2 >> ((unsigned __int8)v16 + 1); v19 = v16; v16 += 2; v17 |= (v18 & 1) << v19; } while ( v16 - 1 < 0x40 ); a2 = v17; } return v4; ``` Cuối cùng, sau bước cộng dồn thì hàm thực hiện hai vòng lặp `do...while` để cập nhật biến `a1` và `a2`.Ở vòng đầu tiên: chuyển bit thứ i của a1 lên vị trí i + 1, nó giống như phép dịch trái: a1 = a1 << 1. Vòng thứ hai: chuyển bit thứ i của a2 xuống vị trí i - 1, giống với phép dịch phải: a2 = a2 >> 1 Sau các đoạn code trên thì em biết là hàm này chính là hàm **Nhân kiểu Nga (Russian Peasant Multiplication)** được làm rối bằng các phép cộng số học cùng với dịch trái phải bitwise. Bản chất hàm này chỉ là phép nhân đơn giản `a1` * `a2`. Qua đó tiến hành chuẩn bị cho mình 1 script để đọc data của các mảng trong ida và giải challenge: ```cpp #include <idc.idc> static Inv(n) { auto x = (3 * n) ^ 2; x = x * (2 - n * x); x = x * (2 - n * x); x = x * (2 - n * x); x = x * (2 - n * x); return x * (2 - n * x); } static main() { auto i, val, key, flag = ""; auto base = 0x140004430; auto unk = 0x14000AB00; for (i = 0; i < 23; i++) { key = Qword(base + 64 + i * 8) * Qword(unk + i * 8) * Qword(base + 512 + i * 8); val = Qword(base + 256 + i * 8) * Inv(key); flag = flag + sprintf("%c%c%c%c%c%c%c%c", (val >> 24) & 0xFF, (val >> 16) & 0xFF, (val >> 48) & 0xFF, (val >> 8) & 0xFF, (val >> 32) & 0xFF, (val >> 40) & 0xFF, val & 0xFF, (val >> 56) & 0xFF); } Message("%s\n", flag); } ``` **Flag** `KCSC{Welcome_to_the_RE_category_in_this_CTF!_Thank_you_for_fighting_until_the_very_end_of_this_challenge._If_you_are_First_Blood,_you_earn_a_100k_bounty._Congrats!_Stay_sharp,_hacker!}` ![image](https://hackmd.io/_uploads/ByKqT23Mbg.png) ## keyGenME ### sub_140001154() Em thấy format giống như write up bài đầu tiên ở trên, qua đó click vào `sub_14000E6E0()` thì chẳng qua đây là hàm entry point wrapper, có nhiệm vụ lấy hInstance, xử lý dòng lệnh (command line) để tách tham số, và thiết lập trạng thái hiển thị cửa sổ (nCmdShow). Cuối cùng, nó gọi hàm `sub_140002C82()`, có thể đây là hàm WinMain thực sự ### sub_140002C82() ```cpp picce.dwSize = 8; picce.dwICC = 0x4000; InitCommonControlsEx(&picce); v6 = VirtualAlloc(0, 0x110u, 0x3000u, 0x40u); lpAddress = v6; if ( v6 ) { *v6 = qword_140016600; v6[33] = qword_140016708; qmemcpy( (void *)((unsigned __int64)(v6 + 1) & 0xFFFFFFFFFFFFFFF8uLL), (const void *)((char *)&qword_140016600 - ((char *)v6 - ((unsigned __int64)(v6 + 1) & 0xFFFFFFFFFFFFFFF8uLL))), 8LL * (((unsigned int)v6 - (((_DWORD)v6 + 8) & 0xFFFFFFF8) + 272) >> 3)); ``` Đoạn code này có điểm đáng ngờ: trong đoạn code `v6 = VirtualAlloc(0, 0x110u, 0x3000u, 0x40u);`, em có tham khảo trên AI thì có kết quả sau: ![image](https://hackmd.io/_uploads/BJgJK63fbe.png) ![image](https://hackmd.io/_uploads/ry2xFahzZg.png) Với việc truyền tham số 0x40 tức **PAGE_EXECUTE_READWRITE** thì hàm này thực hiện việc copy dữ liệu từ vùng nhớ tĩnh `(qword_140016600)` vào vùng nhớ mới cấp phát. Biến `lpAddress` có thể được sử dụng bởi các hàm khác ```cpp wcscpy(ClassName, L"KCSCKeyGenClass"); *(_QWORD *)&WndClass.style = 0; *(_QWORD *)&WndClass.cbClsExtra = 0; *(_OWORD *)&WndClass.hbrBackground = 0; WndClass.lpfnWndProc = (WNDPROC)sub_1400022F2; WndClass.hInstance = hInstance; WndClass.lpszClassName = ClassName; WndClass.hCursor = LoadCursorW(0, (LPCWSTR)0x7F00); WndClass.hbrBackground = 0; WndClass.hIcon = LoadIconW(0, (LPCWSTR)0x7F00); v7 = RegisterClassW(&WndClass); result = 0; if ( v7 ) ``` đoạn code trên thực hiện việc tạo cửa sổ trong windows, để ý kỹ đoạn code này: `WndClass.lpfnWndProc = (WNDPROC)sub_1400022F2;`, `lpfnWndProc` là viết tắt của **Long Pointer to Function Window Procedure**, có thể nói khi click chuộc hay gõ phím,... thì hàm `sub_1400022F2()` sẽ xử lý. Qua đó em tiến hành phân tích hàm `sub_1400022F2()` ### sub_1400022F2() Hàm này thực hiện việc xử lý các thao tác của em trong cửa sổ, lướt xem đoạn code dưới đây: ```cpp v9 = CreateWindowExW(0, "S", L"Username:", 0x50000000u, 30, 80, 100, 20, hWndParent, 0, WindowLongPtrW, 0); SendMessageW(v9, 0x30u, h, 1); qword_140020F50 = CreateWindowExW( 0x200u, L"EDIT", &word_140017222, 0x50000080u, 140, 75, 320, 30, hWndParent, (HMENU)1, WindowLongPtrW, 0); SendMessageW(qword_140020F50, 0x30u, h, 1); ``` Ngoài việc tạo nhãn, màu thì còn có chức năng khác là lưu Handle sau khi nhập vào biến `qword_140020F50` ```cpp v10 = CreateWindowExW(0, "S", L"Serial Key:", 0x50000000u, 30, 125, 100, 20, hWndParent, 0, WindowLongPtrW, 0); SendMessageW(v10, 0x30u, h, 1); qword_140020F48 = CreateWindowExW( 0x200u, L"EDIT", &word_140017222, 0x50000080u, 140, 120, 320, 30, hWndParent, (HMENU)2, WindowLongPtrW, 0); SendMessageW(qword_140020F48, 0x30u, h, 1); qword_140020F40 = CreateWindowExW( 0, L"BUTTON", L"Activate License", 0x5000000Bu, 175, 170, 150, 40, hWndParent, (HMENU)3, WindowLongPtrW, 0); SendMessageW(qword_140020F40, 0x30u, h, 1); SetWindowSubclass(qword_140020F40, pfnSubclass, 0, 0); v11 = CreateWindowExW( 0, "S", L"Enter your username and serial key to activate", 0x50000001u, 0, 230, 500, 20, hWndParent, 0, WindowLongPtrW, 0); FontW = CreateFontW(16, 0, 0, 0, 400, 0, 0, 0, 1u, 0, 0, 5u, 0, "S"); SendMessageW(v11, 0x30u, (WPARAM)FontW, 1); return 0; } return DefWindowProcW(hWndParent, a2, a3, a4); } ``` đoạn code trên cũng có chức năng hoàn thiện ô nhập Serial Key và Nút bấm y hệt như em đã giải thích. Quan trọng là ở phần Button, nếu click vào sẽ thực hiện việc kiểm tra. Sau khi phân tích 2 hàm trên, thì bước tiếp theo là em phân tích xem bên trong `lpAddress` sẽ thực thi gì: ![image](https://hackmd.io/_uploads/SkBQWR2fZg.png) thay vì dịch đoạn shellcode này, em tiến hành dumb code bên trong lpAddress bằng script IDA. ```cpp #include <idc.idc> static main() { auto ea = 0x140016600; auto size = 0x110; auto i; auto f = fopen("C:\\Users\\TYL3R\\Documents\\ctf\\event\\kcsc-recruitment\\keyGenMe\\dump.bin", "wb"); if (f) { for (i = 0; i < size; i++) { fputc(Byte(ea + i), f); } fclose(f); } } ``` sau khi dump thì em lấy file `dump.bin` đấy vào ida phân tích tiếp ```cpp= __int64 __fastcall sub_0(__int64 a1, __int64 a2, __int64 a3, _BYTE *a4, __int64 a5) { __int64 v6; // rax __int64 v7; // rdx int v8; // r11d __int64 result; // rax __int64 v10; // r10 unsigned int v11; // r9d if ( a5 == 0 || a3 == 0 ) return 0; v6 = 0; if ( !a4 ) return 0; do { if ( !a4[v6] ) break; ++v6; } while ( v6 != 256 ); v7 = 0; while ( *(_BYTE *)(a3 + v7) ) { if ( ++v7 == 256 ) { v8 = 256; goto LABEL_10; } } v8 = v7; LABEL_10: if ( !v8 || (_DWORD)v6 != 12 ) return 0; result = 0; if ( *a4 == 107 && a4[2] == 97 && a4[4] == 51 && a4[6] == 51 && a4[8] == 53 && a4[10] == 110 && a4[1] == 109 && a4[3] == 114 && a4[5] == 118 && a4[7] == 114 && a4[9] == 33 && a4[11] == 57 ) { v10 = 0; v11 = 4919; while ( 1 ) { v11 = (v11 ^ *(char *)(a3 + v10) ^ (unsigned __int8)a4[(int)v10 % 12]) + (unsigned __int8)a4[v11 % 0xC]; if ( *(_DWORD *)(a5 + 4 * v10) != v11 ) break; if ( v8 <= (int)++v10 ) return 1; } return 0; } return result; } ``` Ta thấy đoạn kiểm tra username (a4) với các ký tự ASCII cố định --> username bắt buộc: kmar3v3r5!n9 đoạn code bên dưới chính là thuật toán của shell code dựa vào code ta có công thức: `v11 = (v11 ^ a3[v10] ^ a4[v10 % 12]) + a4[v11 % 12]` Tiến hành viết script để giải, trước `qword_140016600` là `unk_140016520` chính là mảng S-Box cần tìm ### Script ```python user = b"kmar3v3r5!n9" a5 = [ 0x1350,0x13E9,0x13FC,0x1400,0x1453,0x1489,0x14F7,0x1529, 0x15A0,0x15DF,0x166D,0x1677,0x169B,0x1733,0x17B8,0x1803, 0x1875,0x1858,0x188D,0x18F3,0x1905,0x199D,0x1A39,0x1AC8, 0x1AC1,0x1AB9,0x1B23,0x1B6E,0x1B9A,0x1C2F,0x1CBB, 0x1D36,0x1DA8,0x1E02,0x1EA2,0x1EFF,0x1F27,0x1F90, 0x1FEC,0x201E,0x20D9,0x20BB,0x212D,0x212E,0x21A4, 0x2200,0x228D,0x2366,0x239F,0x0110 ] def gen_key(): v11 = 4919 key = [] for i, target in enumerate(a5): for c in range(32, 127): test = (v11 ^ c ^ user[i % 12]) + user[v11 % 12] if test == target: key.append(c) v11 = test break else: break return bytes(key) k = gen_key() print("Username:", user.decode()) print("Key :", k.decode()) ``` ![image](https://hackmd.io/_uploads/HJE6sAnMZg.png) ![image](https://hackmd.io/_uploads/SJ7120hzZg.png) ![image](https://hackmd.io/_uploads/ryPehR2MZe.png) **Flag**: `KCSC{C0n9r4tu14t!0n5_Y0u_H4v3_Succ355fu11y_4ct!v4t4t3d_7h3_L!c3n53_S0ftw4r3_W!th_RC4_4nd_D3c0d3_Base64_3_T!m3s__=)))}` ## LLM ![image](https://hackmd.io/_uploads/rkE9xi6z-g.png) ```cpp= int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v3; // rdx __int64 v4; // r8 int v6; // [rsp+20h] [rbp-78h] unsigned __int8 *v7; // [rsp+28h] [rbp-70h] _BYTE *Block; // [rsp+30h] [rbp-68h] __int64 v9; // [rsp+48h] [rbp-50h] __int64 v10; // [rsp+50h] [rbp-48h] __int64 v11; // [rsp+58h] [rbp-40h] BYREF __int64 v12; // [rsp+60h] [rbp-38h] BYREF __int64 v13; // [rsp+68h] [rbp-30h] BYREF __int64 v14; // [rsp+70h] [rbp-28h] BYREF unsigned __int8 v15; // [rsp+78h] [rbp-20h] BYREF v12 = 0; v13 = 0; v11 = 0; v14 = 0; sub_140001070(aEnterKeyToGetF, argv, envp); v7 = (unsigned __int8 *)off_1400040A8; v6 = 0; while ( v7 ) { sub_140001070("Key%d : ", v6); sub_140001140("%d", (unsigned int)&v15); sub_140001230(&v12, &v13, v15, v7[9]); v7 = *(unsigned __int8 **)(v7 + 10); ++v6; } sub_1400012C0(off_1400040A8, v12, &v11, &v14); v9 = sub_1400013F0(v11, 1337); v10 = sub_1400013F0(v11, 9999); if ( v9 == 0xD427202CB4B2LL ) { sub_140001070(aCorrect, v3, v4); Block = malloc(0x31u); sub_140001490(&unk_140004078, 48, v10, Block); Block[48] = 0; sub_140001070("Flag: %s\n", Block); free(Block); } else { sub_140001070(aWrong, v3, v4); } return 0; } ``` Khởi đầu chương trình thì có thể các biến tạm được trả về 0 ban đầu, có 1 biến `v7` là biến con trỏ, biến này trỏ tới `off_1400040A8`, em tạm thời để biến đó qua một bên lát phân tích sau. Tiếp tục tới vòng lặp `while` của con trỏ `v7` tiến hành phân tích: ```cpp while ( v7 ) { sub_140001070("Key%d : ", v6); sub_140001140("%d", (unsigned int)&v15); sub_140001230(&v12, &v13, v15, v7[9]); v7 = *(unsigned __int8 **)(v7 + 10); ++v6; } ``` Dữ liệu mà em nhập vào sẽ được lưu vào biến v15, hàm `sub_140001230` có thể là hàm xử lý thuật toán. Có thể thấy ban đầu biến v7 trỏ đến `off_1400040A8`, khi double click vào `off_1400040A8` thì ta thấy địa chỉ bắt đầu của `v7` là `unk_140003288`, ta xét index của v7[9]: ```bash .rdata:0000000140003288 unk_140003288 db 0 ; DATA XREF: .rdata:off_140003270↑o .rdata:0000000140003288 ; .data:off_1400040A8↓o .rdata:0000000140003289 db 0 .rdata:000000014000328A db 0 .rdata:000000014000328B db 0 .rdata:000000014000328C db 0 .rdata:000000014000328D db 0 .rdata:000000014000328E db 0 .rdata:000000014000328F db 0 .rdata:0000000140003290 db 24h ; $ .rdata:0000000140003291 db 4 .rdata:0000000140003292 dq offset off_140003270 .rdata:000000014000329A align 20h ``` Thấy rằng `v7[9]` lúc này có giá trị = `4`. tại đoạn lệnh `v7 = *(unsigned __int8 **)(v7 + 10);`, lúc này v7 lại trỏ tiếp đến `dq offset off_140003270` tại vòng lặp `sub_140001230(&v12, &v13, v15, v7[9]);` tiếp tục +9 offset: ``` .rdata:0000000140003270 off_140003270 dq offset unk_140003288 ; DATA XREF: .rdata:0000000140003292↓o .rdata:0000000140003270 ; .rdata:off_1400032A0↓o .rdata:0000000140003278 db 12h .rdata:0000000140003279 db 2 .rdata:000000014000327A dq offset off_1400032A0 ``` Tức là bậy giờ giá trị thứ 2 của `v7[9]` là `2`. Tiếp tực thực hiện vòng lặp với việc tìm kiếm dữ liệu tại `v7 + 10` thì tại `off_1400032A0`, các giá trị đều NULL. Như vậy tổng kết thì `v7` có ba hệ số lần lượt và `4, 2, 0` Sau khi tìm được ba hệ số thì hàm `sub_140001230` tiến hành lưu lại ba số đó vào biến `v12`. ```cpp sub_1400012C0(off_1400040A8, v12, &v11, &v14); v9 = sub_1400013F0(v11, 1337); v10 = sub_1400013F0(v11, 9999); ``` Lúc này có thể ba hệ số đã tìm được "trộn" lại lưu vào biến `v11` và bắt đầu thực hiện hàm `sub_1400013F0()` với giá trị `v11` và hằng số `1337` và cho ra kết quả vào biến `v9` đồng thời hàm đó cũng thực hiện giá trị `v11` với hằng số `9999` để tạo kết quả vào biến `v10` ```cpp if ( v9 == 0xD427202CB4B2LL ) { sub_140001070(aCorrect, v3, v4); Block = malloc(0x31u); sub_140001490(&unk_140004078, 48, v10, Block); Block[48] = 0; sub_140001070("Flag: %s\n", Block); free(Block); } else { sub_140001070(aWrong, v3, v4); } ``` Có thể thấy giá trị của biến `v9` đang thực hiện so sánh với hằng số `0xD427202CB4B2LL`, nếu đúng thì in ra `Correct`. Đồng thời biến `v10` có vai trò đóng góp trong quá trình giải mã để tạo ra flag cho chúng ta. Với việc `v9` đóng vai trò quan trọng trong việc so sánh với hằng số và góp phần tạo ra flag, thì tiếp đến em phân tích hàm `sub_1400013F0()` có liên quan đến việc tạo ra `v9` ### sub_1400013F0() ```cpp= __int64 __fastcall sub_1400013F0(__int64 a1, __int64 a2) { int i; // [rsp+0h] [rbp-28h] __int64 v4; // [rsp+8h] [rbp-20h] __int64 v5; // [rsp+10h] [rbp-18h] v5 = 0; while ( a1 ) { v4 = 1; for ( i = 0; i < *(unsigned __int8 *)(a1 + 9); ++i ) v4 *= a2; v5 += v4 * *(unsigned __int8 *)(a1 + 8); a1 = *(_QWORD *)(a1 + 10); } return v5; } ``` Tổng quan thuật toán: hàm này có vòng lặp `for` chạy từ `0` đến `(a1 + 9)`, lấy `v4` * `a2`, Như vậy `v4` = `a2` mũ `(a1 + 9)`. Biến `v5` sẽ lấy `v4` * `(a1 + 8)` rồi cộng dồn vào `v5`. Sau khi thực hiện xong, `a1` nhảy sang node `(a1 + 10)` để lặp lại quy trình. Như vậy công thức tổng quát của `v5` là ***v5 = v5 + (a1 + 8) * a2 mũ (a1 + 9)*** Với ba hệ số mà ta đã tìm được, ta thực hiện phương trình: ***X * 1337 mũ 4 + Y * 1337 mũ 2 + Z * 1337 mũ 0 = 0xD427202CB4B2*** Tiếp đến em cũng phân tích thêm hàm liên quan đến `v10` tức ` sub_140001490()` Để xem cách mà flag được tạo ### sub_140001490() ```cpp unsigned __int64 __fastcall sub_140001490(__int64 a1, unsigned __int64 a2, __int64 a3, __int64 a4) { unsigned __int64 result; // rax unsigned __int64 i; // [rsp+0h] [rbp-28h] for ( i = 0; ; i += 8LL ) { result = a2; if ( i >= a2 ) break; *(_QWORD *)(i + a4) = a3 ^ *(_QWORD *)(i + a1); } return result; } ``` Tổng quát là hàm này thực hiện vòng lặp `for` với bước nhảy `8 byte` có thể thấy hàm này xử lý 8 byte cùng 1 lúc, khi nào hết 48 byte sẽ dừng lại, tức thực hiện vòng lặp 6 lần. Thuật toán chính của hàm này tức là lấy dữ liệu tại địa chỉ `(a1 + i)` ép thành 8 byte (QWORD), sau đó XOR với `a3` sẽ tạo ra kết quả tại địa chỉ `(a4 + i)`. ### Kết luận Tổng quan luồng chương trình sẽ là duyệt link list lấy ba hệ số làm số mũ, sau đó kiểm tra xác thực, nếu đúng sẽ thực hiện kiểm tra với hàm `sub_1400013F0` tạo biến `v9` và `v10`, nếu biến `v9` thỏa với hằng số thì hàm `sub_140001490` sẽ lấy `v10` XOR với dữ liệu mã hóa và tạo ra flag. Dưới đây là script để giải challenge này: ### Script ```python hex_data = ( "49 D2 4F 83 E1 28 4F 7F 5D F9 7D B6 FF 2E 50 6B " "71 E2 79 A4 C5 05 48 6F 5D D5 4F 81 C5 17 49 64 " "63 FD 43 A5 E2 10 4D 2B 23 B0 61 C0 9A 71 20 0A" ) encrypted_bytes = bytes.fromhex(hex_data) target_val = 0xD427202CB4B2 base_check = 1337 base_key = 9999 powers = [4, 2, 0] coeffs = [] remaining = target_val for p in powers: val_pow = base_check ** p coeffs.append(remaining // val_pow) remaining = remaining % val_pow v10 = 0 for i in range(len(powers)): v10 += coeffs[i] * (base_key ** powers[i]) key_bytes = v10.to_bytes(8, 'little') flag = bytearray() for i in range(0, len(encrypted_bytes), 8): chunk = encrypted_bytes[i : i+8] for j in range(len(chunk)): flag.append(chunk[j] ^ key_bytes[j]) try: print(flag.decode('utf-8')) except UnicodeDecodeError: print(flag) ``` **Flag:** `KCSC{You_have_passed_the_DSA_final_exam!!!}` ## medium antidebug Khi nghe tên bài em nghĩ đây là chương trình chống debug, do bản thân em debug khá yếu nên cũng đỡ phải lo vì em nghĩ có thể sử dụng thuật toán để giải challenge này mà không cần debug ### main ```cpp= int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int v3; // eax int v4; // edi unsigned int v5; // edx unsigned int v6; // ecx unsigned int v7; // eax int v9; // [esp-44h] [ebp-48h] int v10; // [esp-30h] [ebp-34h] int v11; // [esp-1Ch] [ebp-20h] __debugbreak(); dword_4043D8 = IsDebuggerPresent(); __debugbreak(); dword_4043D0 = IsDebuggerPresent(); __debugbreak(); v11 = dword_4043D8; IsDebuggerPresent(); __debugbreak(); v10 = dword_4043D8; IsDebuggerPresent(); __debugbreak(); v9 = dword_4043D0; IsDebuggerPresent(); v3 = dword_4043E0; if ( dword_4043E0 ) { if ( byte_40449F[dword_4043E0] == 10 ) v3 = --dword_4043E0; if ( v3 && byte_40449F[v3] == 13 ) dword_4043E0 = --v3; } if ( v3 >= 0x40 ) __report_rangecheckfailure( v9, byte_4044A0, 64, &dword_4043E0, 0, v10, "Enter flag: ", 12, &unk_4043DC, 0, v11, "Welcome to KCSC!\n", 17, &unk_4043DC, 0, -10, -11); byte_4044A0[v3] = 0; __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); dword_4043E8 = (dword_4043E0 + 7) & 0xFFFFFFF8; __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); dword_4043CC = dword_4043E8; if ( dword_4043E8 == 40 ) { v4 = 1; dword_4043EC = 0; v5 = 0; v6 = 0; v7 = 0; while ( byte_4044E0[v7] == byte_403184[v6] ) { v6 = ++v5; dword_4043EC = v5; v7 = v5; if ( v5 >= 0x28 ) goto LABEL_14; } v4 = 0; LABEL_14: if ( v4 ) { __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); } } __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); return 0; } ``` Có thể thấy hàm main gọi rất nhiều hàm check debug. Nhìn xơ qua em đoán là không có hàm thuật toán nào trong hàm main cả. Nhưng em để ý đoạn vòng lặp này: ```cpp while ( byte_4044E0[v7] == byte_403184[v6] ) { v6 = ++v5; dword_4043EC = v5; v7 = v5; if ( v5 >= 0x28 ) goto LABEL_14; } ``` Mảng `byte_403184[v6]` chính là mảnh dữ liệu mà ta cần biến đổi để ra flag, thu thập được data Cũng may là bài này cũng khá ít hàm rải rác nên em thử check hàm thứ 2 trong IDA (tức `sub_401030`) ### sub_401030 ```cpp= char __cdecl sub_401030(int a1, int a2, int a3) { int v3; // esi int v4; // eax int i; // edi char v6; // bl char result; // al v3 = a1; v4 = 0; LOBYTE(a1) = 0; do { *(_BYTE *)(v3 + v4) = v4; ++v4; } while ( v4 < 256 ); for ( i = 0; i < 256; ++i ) { v6 = *(_BYTE *)(v3 + i); a1 = (unsigned __int8)(a1 + *(_BYTE *)(i % a3 + a2) + v6); result = *(_BYTE *)(a1 + v3); *(_BYTE *)(v3 + i) = result; *(_BYTE *)(a1 + v3) = v6; } *(_DWORD *)(v3 + 256) = 0; *(_DWORD *)(v3 + 260) = 0; return result; } ``` Nhìn kỹ hàm `sub_401030()` thì các vòng lặp rất bất thường: ```cpp do { *(_BYTE *)(v3 + v4) = v4; ++v4; } ``` Gán giá trị bằng chính chỉ số index, có thể đây là hàm thuật toán mã hóa. Tiếp tục nhìn các đoạn code bên dưới: ```cpp while ( v4 < 256 ); for ( i = 0; i < 256; ++i ) { v6 = *(_BYTE *)(v3 + i); a1 = (unsigned __int8)(a1 + *(_BYTE *)(i % a3 + a2) + v6); result = *(_BYTE *)(a1 + v3); *(_BYTE *)(v3 + i) = result; *(_BYTE *)(a1 + v3) = v6; } *(_DWORD *)(v3 + 256) = 0; *(_DWORD *)(v3 + 260) = 0; return result; ``` Không nghi ngờ gì thêm nữa đoạn code này đúng là format của KSA RC4. Giai đoạn 1 đã có bây giờ em cần tìm thêm giai đoạn 2 của mã hóa RC4. Tiếp tục phân tích hàm bên dưới ### sub_4010A0 ```cpp void __cdecl sub_4010A0(int a1, int a2, int a3, int a4) { int v4; // ebx int v5; // edi int v6; // ecx char *v7; // esi int v8; // ecx char v9; // dl int v10; // [esp+10h] [ebp+Ch] v4 = a4; if ( a4 > 0 ) { v5 = a3; v10 = a2 - a3; do { ++v5; v6 = (unsigned __int8)(*(_DWORD *)(a1 + 256) + 1); *(_DWORD *)(a1 + 256) = v6; v7 = (char *)(v6 + a1); v8 = (unsigned __int8)(*(_BYTE *)(a1 + 260) + *(_BYTE *)(v6 + a1)); *(_DWORD *)(a1 + 260) = v8; v9 = *v7; *v7 = *(_BYTE *)(v8 + a1); *(_BYTE *)(a1 + *(_DWORD *)(a1 + 260)) = v9; *(_BYTE *)(v5 - 1) = *(_BYTE *)(v5 + v10 - 1) ^ *(_BYTE *)((unsigned __int8)(*(_BYTE *)(*(_DWORD *)(a1 + 256) + a1) + *(_BYTE *)(*(_DWORD *)(a1 + 260) + a1)) + a1); --v4; } while ( v4 ); } } ``` Sau khi nhìn một lúc thì có thể nhận ra đây là RC4 giai đoạn 2 (PRGA), bằng chứng là: ```cpp *(_BYTE *)(v5 - 1) = *(_BYTE *)(v5 + v10 - 1) ^ *(_BYTE *)((unsigned __int8)(*(_BYTE *)(*(_DWORD *)(a1 + 256) + a1) + *(_BYTE *)(*(_DWORD *)(a1 + 260) + a1)) + a1); ``` Đoạn code này lấy dữ liệu đầu vào XOR với dòng khóa, code thực hiện lấy mảng S[i] `*(_BYTE *)(*(_DWORD *)(a1 + 256) + a1)` cộng S[j] `*(_BYTE *)(... a1)` và lấy kết qua đó làm chỉ số index để tra S-box Sau khi thu thập đủ 2 giai đoạn mã hóa RC4, em tiến hành check thêm các hàm có trong IDA lần nữa để xem có các loại mã hóa nào còn không ### sub_401120 ```cpp int __cdecl sub_401120(void *Src, int Size, int a3, _DWORD *a4) { int v4; // ebx int v5; // edi int result; // eax unsigned int v7; // esi unsigned int v8; // edi int v9; // eax bool v10; // zf int v11; // [esp+10h] [ebp-50h] unsigned int *v12; // [esp+14h] [ebp-4Ch] int v13; // [esp+18h] [ebp-48h] _BYTE v14[64]; // [esp+1Ch] [ebp-44h] BYREF v4 = a3; v12 = (unsigned int *)a3; v5 = (Size + 7) / 8; v11 = v5; if ( 8 * v5 ) memset(v14, 0, 8 * v5); if ( Size ) memcpy(v14, Src, Size); result = 8; if ( Size % 8 != 8 ) result = 8 - Size % 8; if ( result > 0 ) result = (int)memset(&v14[Size], result, result); if ( v5 > 0 ) { result = (int)&v14[-a3]; do { v7 = *(_DWORD *)(result + v4); v8 = *(_DWORD *)(result + v4 + 4); v9 = 0; v13 = 32; do { v9 -= 1640531527; v7 += (v9 + v8) ^ (*a4 + 16 * v8) ^ (a4[1] + (v8 >> 5)); v8 += (v9 + v7) ^ (a4[2] + 16 * v7) ^ (a4[3] + (v7 >> 5)); --v13; } while ( v13 ); result = (int)&v14[-a3]; *v12 = v7; v12[1] = v8; v4 = (int)(v12 + 2); v10 = v11-- == 1; v12 += 2; } while ( !v10 ); } return result; } ``` Thực ra em không biết đây là dạng thuật toán gì, em có hỏi chat thử thì đây là thuật toán `TEA (Tiny Encryption Algorithm)`, dấu hiệu nhận biết rõ rệt nhất chính là hằng số được ngụy trang dưới dạng số âm ` v9 -= 1640531527`. Việc xác định số bit để chuyển số âm này sang dạng hex theo cách khai báo biến từ đầu hàm: `int v9; // eax`, thanh ghi eax chứa dữ liệu 32 bit, thực hiện quy tắc bù 2 để chuyển đổi: **Hex = 2 mũ 32 + giá trị âm**, kết quả giá trị hex là `0x9e3779b9` hoàn toàn khớp với hằng số của thuật toán. Kiểm tra các hàm còn lại thì không thấy hàm nào liên quan đến thuật toán. Có một hàm tên `Handler` mà em chưa phân tích từ đầu bài. Vì vậy hãy phân tích nó. ### Handler ```cpp LONG __stdcall Handler(struct _EXCEPTION_POINTERS *ExceptionInfo) { PCONTEXT ContextRecord; // esi PVOID v2; // eax if ( ExceptionInfo->ExceptionRecord->ExceptionCode != -2147483645 ) return 0; ContextRecord = ExceptionInfo->ContextRecord; sub_4012F0(); if ( dword_4043E4 < 15 ) { sub_401240(off_4031B4[dword_4043E4]); ++dword_4043E4; } if ( Handle ) RemoveVectoredExceptionHandler(Handle); v2 = AddVectoredExceptionHandler(1u, sub_4013F0); ++ContextRecord->Eip; Handle = v2; return -1; } ``` Vì kiến thức có hạn nên em tìm hiểu thử về tên hàm Handler, thì khái quát đây là một hàm Win API với loại khai báo `LONG __stdcall Handler(struct _EXCEPTION_POINTERS *ExceptionInfo)`, đây là loại khai báo chuẩn của VEH Logic chính của VEH này nằm ở dòng lệnh gọi hàm `sub_401240` với tham số lấy từ `off_4031B4`. Có thể `off_4031B4` chính là chìa khóa chứa danh sách các hàm. ``` .rdata:004031B4 off_4031B4 dd offset GetStdHandle ; DATA XREF: Handler+24↑r .rdata:004031B4 ; sub_4013F0+24↑r ... .rdata:004031B8 dd offset GetStdHandle .rdata:004031BC dd offset WriteConsoleA .rdata:004031C0 dd offset WriteConsoleA .rdata:004031C4 dd offset ReadConsoleA .rdata:004031C8 dd offset WriteConsoleA .rdata:004031CC dd offset sub_401030 .rdata:004031D0 dd offset sub_4010A0 .rdata:004031D4 dd offset sub_401120 .rdata:004031D8 dd offset sub_401030 .rdata:004031DC dd offset sub_4010A0 .rdata:004031E0 dd offset WriteConsoleA .rdata:004031E4 dd offset ExitProcess .rdata:004031E8 dd offset WriteConsoleA .rdata:004031EC dd offset ExitProcess ``` Dựa vào `off_4031B4`, em đã biết được luồng thực thi của chương trình. Quang trọng hơn chính là giai đoạn gọi các hàm mã hóa mà em đã phân tích ở trên. Như vậy quy trình mã hóa sẽ theo trình tự: ***Input -> RC4 (KSA và PRGA) -> TEA -> RC4 (KSA và PRGA) -> Final*** ### Truy xuất dữ liệu Việc cuối cùng mà em làm chính là truy tìm Key của các loại mã hóa. Đặc trưng của mã hóa TEA chính là key của nó có 16 byte. Cũng may là bên dưới `off_4031B4` đã có mảng `unk_4031F0` chuẩn 16 byte ```bash .rdata:004031F0 unk_4031F0 db 0EFh ; DATA XREF: _main+F0↑o .rdata:004031F1 db 0BEh .rdata:004031F2 db 0ADh .rdata:004031F3 db 0DEh .rdata:004031F4 db 0BEh .rdata:004031F5 db 0BAh .rdata:004031F6 db 0FEh .rdata:004031F7 db 0CAh .rdata:004031F8 db 37h ; 7 .rdata:004031F9 db 13h .rdata:004031FA db 37h ; 7 .rdata:004031FB db 13h .rdata:004031FC db 0Dh .rdata:004031FD db 0F0h .rdata:004031FE db 0ADh .rdata:004031FF db 0BAh ``` Tiếp tục truy vết 2 mảng còn lại thì đây chắc chắn là key RC4, còn về thứ tự key thì `unk_4031AC` được tham chiếu tại `_main+B5` trong khi `unk_403178` được tham chiếu tại `_main+10D`. Vì Code chạy tuần tự, `main+B5` chạy trước -> key này dùng cho RC4 đầu tiên. `main+10D` chạy sau -> dùng cho RC4 cuối cùng. Do đó khi giải ngược thì phải dùng ey ở `main+10D` trước. ```bash .rdata:004031AC unk_4031AC db 1Ah ; DATA XREF: _main+B5↑o .rdata:004031AD db 89h .rdata:004031AE db 14h .rdata:004031AF db 92h .rdata:004031B0 db 22h ; " .rdata:004031B1 db 5Dh ; ] .rdata:004031B2 db 4Fh ; O .rdata:004031B3 db 0 ``` ```bash .rdata:00403178 unk_403178 db 36h ; 6 ; DATA XREF: _main+10D↑o .rdata:00403179 db 0BDh .rdata:0040317A db 0D1h .rdata:0040317B db 3Bh ; ; .rdata:0040317C db 9Ah .rdata:0040317D db 0A8h .rdata:0040317E db 0EFh .rdata:0040317F db 29h ; ) .rdata:00403180 db 0FFh .rdata:00403181 db 0 .rdata:00403182 db 0 .rdata:00403183 db 0 ``` Mảng dữ liệu để giải mã: ```bash .rdata:00403184 byte_403184 db 7Bh ; DATA XREF: _main+166↑r .rdata:00403185 db 6Fh ; o .rdata:00403186 db 0B4h .rdata:00403187 db 0F7h .rdata:00403188 db 81h .rdata:00403189 db 3Dh ; = .rdata:0040318A db 0EFh .rdata:0040318B db 0EEh .rdata:0040318C db 0B2h .rdata:0040318D db 1Eh .rdata:0040318E db 7Eh ; ~ .rdata:0040318F db 8Dh .rdata:00403190 db 0Ch .rdata:00403191 db 9Dh .rdata:00403192 db 67h ; g .rdata:00403193 db 41h ; A .rdata:00403194 db 0CBh .rdata:00403195 db 20h .rdata:00403196 db 53h ; S .rdata:00403197 db 0FDh .rdata:00403198 db 47h ; G .rdata:00403199 db 71h ; q .rdata:0040319A db 0ADh .rdata:0040319B db 0C6h .rdata:0040319C db 0BCh .rdata:0040319D db 0Eh .rdata:0040319E db 0F2h .rdata:0040319F db 0A2h .rdata:004031A0 db 69h ; i .rdata:004031A1 db 4 .rdata:004031A2 db 0A3h .rdata:004031A3 db 0B2h .rdata:004031A4 db 5Eh ; ^ .rdata:004031A5 db 0D7h .rdata:004031A6 db 0D0h .rdata:004031A7 db 6Eh ; n .rdata:004031A8 db 63h ; c .rdata:004031A9 db 6Fh ; o .rdata:004031AA db 72h ; r .rdata:004031AB db 9Eh ``` ### Script ```python import struct from Crypto.Cipher import ARC4 cipher = bytes([ 0x7B, 0x6F, 0xB4, 0xF7, 0x81, 0x3D, 0xEF, 0xEE, 0xB2, 0x1E, 0x7E, 0x8D, 0x0C, 0x9D, 0x67, 0x41, 0xCB, 0x20, 0x53, 0xFD, 0x47, 0x71, 0xAD, 0xC6, 0xBC, 0x0E, 0xF2, 0xA2, 0x69, 0x04, 0xA3, 0xB2, 0x5E, 0xD7, 0xD0, 0x6E, 0x63, 0x6F, 0x72, 0x9E ]) key_rc4_outer = bytes([ 0x36, 0xBD, 0xD1, 0x3B, 0x9A, 0xA8, 0xEF, 0x29, 0xFF ]) key_tea = bytes([ 0xEF, 0xBE, 0xAD, 0xDE, 0xBE, 0xBA, 0xFE, 0xCA, 0x37, 0x13, 0x37, 0x13, 0x0D, 0xF0, 0xAD, 0xBA ]) key_rc4_inner = bytes([ 0x1A, 0x89, 0x14, 0x92, 0x22, 0x5D, 0x4F ]) def decrypt_tea(data, key): v = list(struct.unpack("<" + "I" * (len(data) // 4), data)) k = struct.unpack("<4I", key) delta = 0x9E3779B9 for i in range(0, len(v), 2): v0, v1 = v[i], v[i+1] sum_val = (delta * 32) & 0xFFFFFFFF for _ in range(32): v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + sum_val) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + sum_val) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF sum_val = (sum_val - delta) & 0xFFFFFFFF v[i], v[i+1] = v0, v1 return struct.pack("<" + "I" * len(v), *v) s1 = ARC4.new(key_rc4_outer).decrypt(cipher) s2 = decrypt_tea(s1, key_tea) flag = ARC4.new(key_rc4_inner).decrypt(s2) print(flag) ``` --- *Write up của em có thể một phần sơ sài, và em viết cũng rất lâu. Lý do là các challenge em giải được chủ yếu em dựa vào AI, đồng thời em cũng yếu thuật toán nên em phải giải lại từng challenge để hiểu dễ rõ hơn về thuật toán và cách mà chương trình hoạt động. Các script thì cũng có phần dựa vào AI nốt. Nhưng dù vậy thì em rất cảm ơn anh chị và CLB đã cho em một cuộc thi ctf thật tuyệt vời, các challenge rất hay và có các challenge lần đầu em gặp trong quá trình học reverse với thời gian ít ỏi vừa qua. Có thể nói đây là giải ctf đầu tiên em chơi suốt 24 giờ mà không nghỉ, và write up này cũng là bài viết tâm huyết nhất của em. Dù bản thân hiểu biết có hạn về code, thuật toán nhưng qua giải ctf này em học được rất nhiều kiến thức hay và bổ ích. Một lần nữa em cảm ơn các anh chị ban chuyên môn, cảm ơn KCSC*