KCSC REcruitment 2026 === ## 1. puRE Description: ``` No math just 'p' 67.223.119.69 5025 ``` Chương trình là file elf 64 chạy trên linux. Bài này ai từng chơi rubik sẽ dễ hiểu hơn (maybe). Sau khi đưa file vào ida, ta có logic như sau: <details> <summary>main</summary> ```cpp= __int64 __fastcall main(int a1, char **a2, char **a3) { __int64 v3; // rsi char flag_buffer[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 *input_rubik_step; // [rsp+700h] [rbp-10h] __int64 i; // [rsp+708h] [rbp-8h] seed = time(0LL); srand(seed); puts("======================================================="); puts("= PUZZLE CTF CHALLENGE ="); puts("======================================================="); reset_rubik("=======================================================", a2); while ( 1 ) { printf("\n>> "); if ( !fgets(s, 1000, stdin) ) break; s[strcspn(s, "\n")] = 0; if ( s[0] ) { strcpy(dest, s); for ( i = 0LL; dest[i]; ++i ) dest[i] = tolower((unsigned __int8)dest[i]); if ( !strcmp(dest, "quit") || !strcmp(dest, "exit") ) { puts("Bye!"); return 0LL; } if ( !strcmp(dest, "solution") ) { if ( v_scramble_step ) print_solution(); else puts("[!] Doc code chua ma go command linh tinh?"); } else if ( !strcmp(dest, "test") ) { reset_rubik(dest, "test"); v_challengemodegogo = 0; scramble(36LL); printf_cube(); } 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 ( v_unlock_challenge ) { puts("\n[!] Starting REAL CHALLENGE..."); puts("[!] Scrambling 1836 times. Du trinh ko???\n"); reset_rubik("[!] Scrambling 1836 times. Du trinh ko???\n", "challenge"); puts("[!] Reset challenge...Done\n"); v_challengemodegogo = 1; scramble(1836LL); } else { puts("\n[!] You must complete TEST mode first!"); } } else if ( v_scramble_step ) { v8 = 0LL; input_rubik_step = strtok(s, " "); v10 = 0; while ( input_rubik_step ) { if ( (unsigned int)apply_move(input_rubik_step) ) ++v10; else printf("[!] Unknown: %s\n", input_rubik_step); input_rubik_step = strtok(0LL, " "); } if ( v10 > 0 ) { if ( (unsigned int)is_solve() ) { putchar(10); puts("================================================"); puts("= CONGRATULATIONS! CHALLENGE SOLVED! ="); puts("================================================"); if ( v_challengemodegogo ) { read_flag(flag_buffer, 256); printf(" FLAG: %s\n", flag_buffer); v3 = (unsigned int)counter; printf(" Total moves: %d\n", counter); putchar(10); puts(" You are a TRUE Rot's Master!"); } else { putchar(10); puts(" Nice! You solved the practice cube!"); v3 = (unsigned int)counter; printf(" Total moves: %d\n", counter); putchar(10); puts(" >> You can now access CHALLENGE mode!"); puts(" >> Type 'challenge' for the REAL challenge!"); v_unlock_challenge = 1; } puts("================================================"); if ( v_challengemodegogo ) return 0LL; reset_rubik("================================================", v3); v_challengemodegogo = 0; v_scramble_step = 0; } else { if ( counter > 998 ) { puts("\n[!] Too many moves! This puzzle is not that kind of ez=)))"); return 0xFFFFFFFFLL; } printf("Moves: %d\n", counter); } } } else { puts("[!] Doc code chua ma go tinh tinh zay"); } } } return 0LL; } ``` </details> OK vậy đây là dạng tương tác với chương trình. Đầu tiên đập vào mắt ta là ![image](https://hackmd.io/_uploads/rk5IRx3zWl.png) Chương trình sử dụng bộ sinh số (không) ngẫu nhiên dựa trên thời gian hiện tại 0LL. Vậy nếu biết được thời gian chạy của server thì mình có thể đoán được chuỗi số ngẫu nhiên. ![image](https://hackmd.io/_uploads/HyGmyW3MZl.png) While (1) là while true, gameloop. Chương trình sẽ liên tục chờ người dùng nhập lệnh. ![image](https://hackmd.io/_uploads/SkaakZnMbe.png) quit, exit thoát chương trình. ![image](https://hackmd.io/_uploads/HykE6GnGZg.png) test gọi 3 hàm. ![image](https://hackmd.io/_uploads/Hkcdxb3fZg.png) 1. Hàm đặt lại trạng thái rubik với các mặt WYORGB hoàn thiện (white, yellow, orange,...) 2. Hàm xoay rubik (scramble bên dưới) 3. Hàm in trạng thái rubik ![image](https://hackmd.io/_uploads/r1pm7-3M-g.png) challenge gọi hàm reset và scramble. Tại sao lại 1836 bước 😡? Có vẻ phải hoàn thành chế độ test trước thì biến v_challengemodegogo mới được bật thành 1. (mình để v_ trước tên cho bản thân dễ nhận biết cái nào là biến) ![image](https://hackmd.io/_uploads/HkHEdZ3G-g.png) Nếu input không phải các lệnh trên `else if ( v_scramble_step )` thì chương trình sẽ coi là các bước của người chơi. input_rubik_step dùng strtok để tách chuỗi theo dấu cách, kiểu như "L L R" thành "L", "L", "R". kiểm tra thực hiện hàm xoay rubbik với tham số input_rubbik_step: ``` if ( (unsigned int)apply_move(input_rubik_step) ) ``` sau đó tăng biến v10 lên, để bên dưới sẽ dùng v10 để kiểm tra người dùng có nhập bước hợp lệ không. ![image](https://hackmd.io/_uploads/rkrf9bhf-x.png) ![image](https://hackmd.io/_uploads/ryarcb2Mbl.png) Nếu biến v_challengemodegogo khác 0 thì in ra flag (biến hồi nãy được ++ khi giải được lệnh test). Ngược lại thì in ra đã giải practice cube, có thể access Challenge mode. ![image](https://hackmd.io/_uploads/rkaroWnMZx.png) Giới hạn bước là 998, mà ở challenge cmd nó lại xáo trộn 1836 (var?) bước. ![image](https://hackmd.io/_uploads/BkPCsb2Mbe.png) Vậy không sài solution cmd để giải được rồi. ```cpp= __int64 __fastcall scramble(int number_moves) { __int64 result; // rax char *src[12]; // [rsp+10h] [rbp-70h] int v3; // [rsp+70h] [rbp-10h] int v4; // [rsp+74h] [rbp-Ch] int i; // [rsp+78h] [rbp-8h] int j; // [rsp+7Ch] [rbp-4h] src[0] = "U"; src[1] = "U'"; src[2] = "D"; src[3] = "D'"; src[4] = "L"; src[5] = "L'"; src[6] = "R"; src[7] = "R'"; src[8] = "F"; src[9] = "F'"; src[10] = "B"; src[11] = "B'"; if ( number_moves > 50 ) { printf("[!] Scrambling with % d moves", number_moves); for ( i = 0; i < number_moves; ++i ) { v4 = rand() % 12; strcpy((char *)&v_scramble_history + 10 * i, src[v4]); apply_move(src[v4]); if ( !(i % 100) ) putchar(46); } puts(" Done!"); } else { printf("[!] Scramble (%d moves): ", number_moves); for ( j = 0; j < number_moves; ++j ) { v3 = rand() % 12; strcpy((char *)&v_scramble_history + 10 * j, src[v3]); apply_move(src[v3]); printf("%s ", src[v3]); } putchar(10); } result = (unsigned int)number_moves; v_scramble_step = number_moves; counter = 0; return result; } ``` Hàm scramble: - `strcpy((char *)&v_scramble_history + 10 * i, src[v4])` được dùng để lưu lại các bước đã xáo trộn. Đây cũng là mảng mà lệnh solution sẽ đọc ra để in đáp án. - v_scramble_step lưu lại tổng số bước xáo. - counter reset về 0 khi bắt đầu xáo trộn mới Sau khi hỏi AI và tìm đọc thì mình kiếm được thuật toán kociemba để tìm các bước xoay ngắn nhất. Script giải mã áp dụng kociemba: ```python= import kociemba U, R, F, D, L, B = 0, 9, 18, 27, 36, 45 def move(s, m): st = list(s) def rot(i): t = st[i:i+9] st[i:i+9] = [t[6], t[3], t[0], t[7], t[4], t[1], t[8], t[5], t[2]] if m == 'U': rot(U); st[B:B+3], st[L:L+3], st[F:F+3], st[R:R+3] = st[L:L+3], st[F:F+3], st[R:R+3], st[B:B+3] elif m == 'D': rot(D); st[F+6:F+9], st[L+6:L+9], st[B+6:B+9], st[R+6:R+9] = st[L+6:L+9], st[B+6:B+9], st[R+6:R+9], st[F+6:F+9] elif m == 'R': rot(R); t=[st[U+2],st[U+5],st[U+8]]; st[U+2],st[U+5],st[U+8]=st[F+2],st[F+5],st[F+8]; st[F+2],st[F+5],st[F+8]=st[D+2],st[D+5],st[D+8]; st[D+2],st[D+5],st[D+8]=st[B+6],st[B+3],st[B+0]; st[B+6],st[B+3],st[B+0]=t elif m == 'L': rot(L); t=[st[U+0],st[U+3],st[U+6]]; st[U+0],st[U+3],st[U+6]=st[B+8],st[B+5],st[B+2]; st[B+8],st[B+5],st[B+2]=st[D+0],st[D+3],st[D+6]; st[D+0],st[D+3],st[D+6]=st[F+0],st[F+3],st[F+6]; st[F+0],st[F+3],st[F+6]=t elif m == 'F': rot(F); t=[st[U+6],st[U+7],st[U+8]]; st[U+6],st[U+7],st[U+8]=st[L+8],st[L+5],st[L+2]; st[L+8],st[L+5],st[L+2]=st[D+2],st[D+1],st[D+0]; st[D+2],st[D+1],st[D+0]=st[R+0],st[R+3],st[R+6]; st[R+0],st[R+3],st[R+6]=t elif m == 'B': rot(B); t=[st[U+2],st[U+1],st[U+0]]; st[U+2],st[U+1],st[U+0]=st[R+8],st[R+5],st[R+2]; st[R+8],st[R+5],st[R+2]=st[D+6],st[D+7],st[D+8]; st[D+6],st[D+7],st[D+8]=st[L+0],st[L+3],st[L+6]; st[L+0],st[L+3],st[L+6]=t return "".join(st) print("Input (Enter 2 lần):") lines = [] while True: line = input() if not line: break lines.append(line) moves = " ".join(lines).replace('\n', ' ').split() state = "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB" for m in reversed(moves): base, suffix = m[0], m[1] if len(m) > 1 else "" count = 1 if suffix == "'" else (2 if suffix == "2" else 3) for _ in range(count): state = move(state, base) short_sol = kociemba.solve(state) final = [] for m in short_sol.split(): final.extend([m[0], m[0]] if '2' in m else [m]) print("output:") print(" ".join(final)) ``` ![image](https://hackmd.io/_uploads/BJTFOG2G-e.png) ![image](https://hackmd.io/_uploads/BJa9Oz3fbx.png) Thực hiện tương tự với lệnh challenge và solution. ![image](https://hackmd.io/_uploads/BJ2ZYfnzbe.png) 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}` 2.LLM --- Description: `llm = linked-list master!` Load vào ida ta có hàm main: <details> <summary>main</summary> ```c= int __fastcall main(int argc, const char **argv, const char **envp) { int i; // [rsp+20h] [rbp-78h] unsigned __int8 *node; // [rsp+28h] [rbp-70h] _BYTE *flag_buffer; // [rsp+30h] [rbp-68h] __int64 check_value; // [rsp+48h] [rbp-50h] __int64 decrypt_key; // [rsp+50h] [rbp-48h] __int64 final_list; // [rsp+58h] [rbp-40h] BYREF __int64 user_input_list; // [rsp+60h] [rbp-38h] BYREF __int64 v11; // [rsp+68h] [rbp-30h] BYREF __int64 v12; // [rsp+70h] [rbp-28h] BYREF unsigned __int8 user_input; // [rsp+78h] [rbp-20h] BYREF user_input_list = 0LL; v11 = 0LL; final_list = 0LL; v12 = 0LL; printf("Enter key to get flag!\n"); node = (unsigned __int8 *)off_1400040A8; i = 0; while ( node ) { printf("Key%d : ", i); scanf("%d", (unsigned int)&user_input); list_append(&user_input_list, &v11, user_input, node[9]); node = *(unsigned __int8 **)(node + 10); ++i; } merge(off_1400040A8, user_input_list, &final_list, &v12); check_value = calculation_hash(final_list, 1337LL); decrypt_key = calculation_hash(final_list, 9999LL); if ( check_value == 0xD427202CB4B2LL ) { printf("Correct!\n"); flag_buffer = malloc(0x31uLL); decrypt_xor(&unk_140004078, 48LL, decrypt_key, flag_buffer); flag_buffer[48] = 0; printf("Flag: %s\n", flag_buffer); free(flag_buffer); } else { printf("Wrong!\n"); } return 0; } ``` </details> ![image](https://hackmd.io/_uploads/H1kXL9hf-g.png) hàm này là logic chính của chương trình, nhận tham số final_list và 1337/9999. Hàm trông có vẻ lạ nhưng đại khái là tính tổng các chữ số trong hệ đếm 1337. Ví dụ 36 trong hệ 10 là 3x10 + 6x1, thì 36 trong hệ 1337 là 3x1337 + 6x1... ![image](https://hackmd.io/_uploads/B1ufS53Mbl.png) Sau khi thực hiện hàm da_thuc trên với số `1337`, thì nó phải bằng `0xD427202CB4B2LL` thì mới ra flag. `D427202CB4B2` hex = `233266200236914`, vậy có tổng số `233266200236914`, làm sao để tìm ra các chữ số đã tạo nên tổng này, biết rằng hệ đếm là `1337`? ta làm ngược lại, lấy `233266200236914` chia `1337` ra số dư (đó là input 1), lấy tiếp phần nguyên vừa ra chia tiếp cho `1337` được số dư (input 2), tương tự với các input còn lại... ![image](https://hackmd.io/_uploads/HynVo52zbx.png) decrypt_xor là hàm giải mã. ![image](https://hackmd.io/_uploads/B1L6o5hGWe.png) Chạy vòng lặp 6 lần, mỗi lần xử lí 8 byte (QWORD là 8 byte) xor với key 8 byte. <details> <summary>key</summary> ```asm= .data:0000000140004078 unk_140004078 db 49h ; I ; DATA XREF: main+166↑o .data:0000000140004079 db 0D2h .data:000000014000407A db 4Fh ; O .data:000000014000407B db 83h .data:000000014000407C db 0E1h .data:000000014000407D db 28h ; ( .data:000000014000407E db 4Fh ; O .data:000000014000407F db 7Fh ;  .data:0000000140004080 db 5Dh ; ] .data:0000000140004081 db 0F9h .data:0000000140004082 db 7Dh ; } .data:0000000140004083 db 0B6h .data:0000000140004084 db 0FFh .data:0000000140004085 db 2Eh ; . .data:0000000140004086 db 50h ; P .data:0000000140004087 db 6Bh ; k .data:0000000140004088 db 71h ; q .data:0000000140004089 db 0E2h .data:000000014000408A db 79h ; y .data:000000014000408B db 0A4h .data:000000014000408C db 0C5h .data:000000014000408D db 5 .data:000000014000408E db 48h ; H .data:000000014000408F db 6Fh ; o .data:0000000140004090 db 5Dh ; ] .data:0000000140004091 db 0D5h .data:0000000140004092 db 4Fh ; O .data:0000000140004093 db 81h .data:0000000140004094 db 0C5h .data:0000000140004095 db 17h .data:0000000140004096 db 49h ; I .data:0000000140004097 db 64h ; d .data:0000000140004098 db 63h ; c .data:0000000140004099 db 0FDh .data:000000014000409A db 43h ; C .data:000000014000409B db 0A5h .data:000000014000409C db 0E2h .data:000000014000409D db 10h .data:000000014000409E db 4Dh ; M .data:000000014000409F db 2Bh ; + .data:00000001400040A0 db 23h ; # .data:00000001400040A1 db 0B0h .data:00000001400040A2 db 61h ; a .data:00000001400040A3 db 0C0h .data:00000001400040A4 db 9Ah .data:00000001400040A5 db 71h ; q .data:00000001400040A6 db 20h .data:00000001400040A7 db 0Ah ``` </details> Script giải mã: ```python= from itertools import cycle enc = bytes([ 0x49, 0xD2, 0x4F, 0x83, 0xE1, 0x28, 0x4F, 0x7F, 0x5D, 0xF9, 0x7D, 0xB6, 0xFF, 0x2E, 0x50, 0x6B, 0x71, 0xE2, 0x79, 0xA4, 0xC5, 0x05, 0x48, 0x6F, 0x5D, 0xD5, 0x4F, 0x81, 0xC5, 0x17, 0x49, 0x64, 0x63, 0xFD, 0x43, 0xA5, 0xE2, 0x10, 0x4D, 0x2B, 0x23, 0xB0, 0x61, 0xC0, 0x9A, 0x71, 0x20, 0x0A ]) val = 0xD427202CB4B2 ad = [] while val: ad.append(val % 1337) val //= 1337 key = sum(c * (9999 ** i) for i, c in enumerate(ad)) key_bytes = key.to_bytes(8, 'little') flag = bytes(a ^ b for a, b in zip(enc, cycle(key_bytes))) print(flag.decode(errors='ignore')) ``` Flag: `KCSC{You_have_passed_the_DSA_final_exam!!!}` 3.Just_EzReversing --- Description: ``` A hidden message could help you or not: "566a497765453548526b64694d334272556b564b62315577576c706c526e424859555a6b546c5978536c7057625842485954466b5231645961474653625768795646524b556d5673634556556258524f545778474e6c5578566d395a565446595657787356324672576e4a574d4670615a555a77523246475a4535574d557061566d786b6132467353586c614d326868556d316f574652575a464e58566c7078556d313057464a736244525861317076566d78766547457a6247685352567079566a4261576d564763456468526d524f566a464b576c5a746345645862466c335632303557465a74556b685a5656707a56305a4b56453957526d786862575179566c566f646b354664336c5862457052566b52424f513d3d" ``` Đây là file pe 64, load vào ida thấy có tls callback_0 và tls callback_1, nhưng may mắn không phải antidebug. Nhưng để chắc ăn thì tôi sử dụng scyllahide và custom sẵn vài exception để làm bài trơn tru. Không thấy hàm main đâu. Thực hiện tìm chuỗi bằng shift f12 và xref sau đó đổi tên hàm biến dựa trên chức năng ta được: ```cpp= __int64 __fastcall check_key(char *input_buffer, const char *a2, __int64 a3, __int64 a4, int a5, int a6) { size_t input_len; // rax __int64 v7; // rdx double v8; // xmm0_8 size_t i; // rbx char *base32_encoded; // rsi size_t chunk_addr; // rdx int v12; // eax __int64 v13; // rdx not_important0((int)input_buffer, (int)a2, a3, a4, a5, a6); printf(input_buffer, a2, aEnterTheFlag, "%s"); __acrt_iob_func((unsigned int)input_buffer); fgets(input_buffer, (int)a2, (FILE *)100); byte_7FF7A0F80340[strcspn(input_buffer, a2)] = 0; input_len = strlen(input_buffer); v8 = (double)(int)input_len; if ( (double)(int)input_len == 36.0 ) { i = 0LL; base32_encoded = base32_encode((__int64)input_buffer, (__int64)a2, input_len, byte_7FF7A0F80340, 1); while ( i < strlen(v_target_hash_data) ) { chunk_addr = (size_t)&base32_encoded[i]; i += 2LL; strncpy(v_target_hash_data, base32_encoded, chunk_addr); md5((__int64)v_target_hash_data, base32_encoded, v_target_hash_data, byte_7FF7A0F80324); v12 = hash_offset; *(__m128i *)((char *)&user_md5_hash + hash_offset) = _mm_load_si128((const __m128i *)v_target_hash_data); hash_offset = v12 + 16; } if ( !memcmp(v_target_hash_data, base32_encoded, (size_t)&Size) ) { printf(v_target_hash_data, base32_encoded, v13, "Correct!\n", v8); return 1LL; } else { printf(v_target_hash_data, base32_encoded, v13, "Incorrect!\n", v8); return 0LL; } } else { printf(input_buffer, a2, v7, "Incorrect length!\n", v8); return 0LL; } } ``` Hàm base32_encode có các đặc điểm nhận dạng khá rõ rệt: - `output_buffer[v14] = aAbcdefghijklmn[(v13 >> (v12 + 3)) & 0x1F]`: AND với 0x1F (31) - Code padding bằng 61 (kí tự = trong ascii) ![image](https://hackmd.io/_uploads/rJDmmV2z-x.png) - Tham chiếu đến mảng aAbcdefghijklmn, đây là bảng chữ cái của base32. ![image](https://hackmd.io/_uploads/Hk3rX42G-g.png) <details> <summary>base32_encode </summary> ```c _BYTE *__fastcall base32_encode(__int64 a1, __int64 a2, __int64 input_len, unsigned __int8 *input_buffer, char padding) { _BYTE *result; // rax _BYTE *output_buffer; // rbx unsigned __int8 *v10; // r10 unsigned __int8 *v11; // rbp int v12; // r9d unsigned int v13; // edx __int64 v14; // rax __int64 v15; // rdi int v16; // ecx __int64 v17; // r9 _BYTE *v18; // rdx result = (_BYTE *)operator new[]((unsigned __int64)input_buffer); output_buffer = result; if ( input_len ) { v10 = input_buffer; v11 = &input_buffer[input_len]; v12 = 0; v13 = 0; v14 = 0LL; do { while ( 1 ) { v15 = v14 + 1; v13 = *v10 | (v13 << 8); v16 = v12 + 3; output_buffer[v14] = aAbcdefghijklmn[(v13 >> (v12 + 3)) & 0x1F]; if ( v12 + 3 <= 4 ) break; v12 -= 2; v14 += 2LL; ++v10; output_buffer[v14 - 1] = aAbcdefghijklmn[(v13 >> v12) & 0x1F]; if ( v11 == v10 ) goto LABEL_6; } v17 = v14; ++v10; ++v14; v15 = v17; v12 = v16; } while ( v11 != v10 ); LABEL_6: if ( v12 ) { output_buffer[v14] = aAbcdefghijklmn[(v13 << (5 - v12)) & 0x1F]; v14 = v15 + 2; } v18 = &output_buffer[v14]; if ( padding && (v14 & 7) != 0 ) { do output_buffer[v14++] = 61; while ( (v14 & 7) != 0 ); v18 = &output_buffer[v14]; } result = output_buffer; *v18 = 0; } else { *result = 0; } return result; } ``` </details> Hàm md5 cũng có đặc điểm nhận dạng rõ ràng: - `v12 += 512LL`: tăng 512 bit (64byte) mỗi lần lặp, đây là độ dài chuẩn của block md5/sha ![image](https://hackmd.io/_uploads/HyX1VNhGbx.png) - Sử dụng xmmword_7FF7A0F7B0D0: Đây là 4 hằng số khởi tạo của md5, dưới dạng kiến trúc little endian x86 64 nên khi hiển thị nó sẽ bị đảo ngược 4 thứ tự. ![image](https://hackmd.io/_uploads/SyooN42M-x.png) ![image](https://hackmd.io/_uploads/BylZwEnMWe.png) <details> <summary>md5</summary> ```c= _BYTE *__fastcall md5(__int64 a1, __int8 *a2, const char *a3, __int8 *a4) { size_t v6; // rax __int64 i; // rdx __int8 v8; // r8 __m128i v10; // [rsp+20h] [rbp-88h] BYREF unsigned int v11; // [rsp+60h] [rbp-48h] __int64 v12; // [rsp+68h] [rbp-40h] __m128i si128; // [rsp+70h] [rbp-38h] si128 = _mm_load_si128((const __m128i *)&xmmword_7FF7A0F7B0D0); v11 = 0; v12 = 0LL; v6 = strlen(a3); if ( v6 ) { a2 = &a4[v6]; for ( i = 0LL; ; i = v11 ) { v8 = *a4; v11 = i + 1; v10.m128i_i8[i] = v8; if ( (_DWORD)i == 63 ) { ++a4; sub_7FF7A0F71700((__int64)a3, (__int64)a2, &v10, &v10); v11 = 0; v12 += 512LL; if ( a4 == a2 ) return sub_7FF7A0F72230((__int64)a3, (__int64)a2, (__int64)a3, (__int64)&v10); } else if ( ++a4 == a2 ) { return sub_7FF7A0F72230((__int64)a3, (__int64)a2, (__int64)a3, (__int64)&v10); } } } return sub_7FF7A0F72230((__int64)a3, (__int64)a2, (__int64)a3, (__int64)&v10); } ``` </details> Chương trình sẽ bắt nhập chuỗi có độ dài 36 kí tự? `if ( (double)(int)input_len == 36.0 )` Sau đó mã hóa input thành base32 `base32_encoded = base32_encode((__int64)input_buffer, (__int64)a2, input_len, byte_7FF7A0F80340, 1);` Chia chuỗi base32_encoded thành các cặp 2 kí tự. Tính hash md5 của từng cặp kí tự ![image](https://hackmd.io/_uploads/HJri0N2f-g.png) Cuối cùng là so sánh các mã hash tính được với hash cứng trong v_target_hash_data. Vì hash là một chiều không dịch ngược được nên mình phải tìm cách khác. Vì đầu vào của md5 chỉ có 2 kí tự, mà một kí tự có 32 trường hợp (base32), vậy 32 . 32 là 1024 trường hợp, bé vl nên brute force được Vậy mình cần hash cứng từ memory (v_target_hash_data trong chế độ mã giả ida bị lú, nếu chịu khó đọc assembly thì hash cứng sẽ nằm trong biến Size nên cũng không cần debug làm gì. Thực tế thanh ghi RDX trỏ vào vùng nhớ Size tại section .data chứa các byte Hash cứng thay vì rcx giữ v_target_hash_data như mã giả hiển thị) ![image](https://hackmd.io/_uploads/Hk23hEhfZx.png) ![image](https://hackmd.io/_uploads/S1c6hN2M-l.png) Mình sẽ thực hiện đặt breakpoint tại hàm memcmp sử dụng x64dbg ![image](https://hackmd.io/_uploads/H1D2T43GWl.png) Mình quên rebase segment bên ida... mà thôi kệ đi, nhìn phần đuôi địa chỉ là được... - Theo kiến trúc x64 thì tham số truyền vào hàm nằm theo thứ tự: RCX, RDX, R8, R9 thay vì truyền hết vào stack như 32bit. ![image](https://hackmd.io/_uploads/BkCF1HnGWx.png) RDX chứa địa chỉ đến hash cứng. Click chọn follow in dump ![image](https://hackmd.io/_uploads/ByIzeS2z-g.png) Ta thực hiện dump 512 byte từ vùng nhớ này. Tại sao là 512 byte? Input dài 36 kí tự, khi encode base32 sẽ thành 58 kí tự, và được padding thêm để tròn 64 kí tự. Tách chuỗi trên thành từng cặp kí tự => còn 32 cặp. Md5 lấy 32 x 16bytes = 512 bytes. Ta có script giải mã bằng bruteforce: ```python= import hashlib import base64 import itertools hex_dump = """ D9 B2 52 86 6A 25 98 6F D8 EC 8A 6C 9B DE D2 75 C7 9B DA 0E 66 F0 7B EE 66 2A 68 40 B9 07 BF 39 8D 36 B3 61 A4 79 44 22 C7 06 81 58 6A 95 73 50 18 2B E0 C5 CD CD 50 72 BB 18 64 CD EE 4D 3D 6E AC CB 66 F0 EC D8 26 AA C8 90 65 99 0E 1D A9 7F 56 9B 3C ED 35 55 BF AE 63 CA DB 65 58 A6 FC BE 33 D2 A0 8E EE 4C 17 81 19 40 FE FB C4 E0 97 78 6B A8 9A B4 18 23 AF 9B 65 EA 5D 23 30 31 B9 F8 E4 A5 9D F8 B9 72 06 10 9E B4 B7 F2 FE 52 8A 4D AD 44 AC 8C 02 28 5F 6B 91 64 A0 84 92 A7 8F 92 0A 5B 65 72 5F 6A 3B A9 58 21 CE C6 1D BB 46 D3 2F F2 D4 5E 98 3C 58 E9 6C 9B A3 3D 69 72 1C E8 51 F5 81 93 77 65 89 0F 2A 70 6C 77 EA 8A F3 CC A3 6C 71 EB 23 9A 02 31 BA 0C 6E 7A 45 90 F0 62 42 AD C7 77 F3 C2 C9 70 05 15 0C B7 B8 61 65 B6 C2 3F A9 99 69 25 B6 10 71 0D 93 E2 8C 59 A3 E2 D5 C4 42 58 D5 16 59 F9 62 79 C4 70 CE 81 85 DC 6E 02 9F BF A8 17 DF C4 58 59 8A 5D 38 A6 75 5F 7C 78 EB B4 C2 23 C9 6F 8E 6C CC 29 F7 3C C2 8E 76 AA 96 36 9A BB BA 52 E6 21 BF A8 3D A8 E6 4F BA 7D 08 34 D2 3B 91 50 4B ED 68 59 01 DC 3A C5 38 F9 9A BB C1 D3 39 C2 77 C0 66 9E 7B C3 73 C0 0A 5B 65 72 5F 6A 3B A9 58 21 CE C6 1D BB 46 D3 85 3B C5 5C 10 41 F2 4B 93 92 06 93 31 E6 53 36 F2 14 A7 D4 2E 0D E5 87 5D 55 18 9E 01 E2 E1 87 7B 7C D2 4E A6 F0 8B 71 1C F4 05 3B EA C4 3C C5 64 F3 BD 17 41 AB 8D 6B A5 45 A1 AE 09 BB 87 28 8A 6B 17 8D 3A F0 A5 A9 B2 74 4C A3 19 21 D5 E2 20 63 84 97 17 D3 2D D1 9E 53 4B 77 CA BA C5 17 20 63 84 97 17 D3 2D D1 9E 53 4B 77 CA BA C5 17 20 63 84 97 17 D3 2D D1 9E 53 4B 77 CA BA C5 17 """ charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=" rainbow_table = { hashlib.md5((c1+c2).encode()).digest(): c1+c2 for c1, c2 in itertools.product(charset, repeat=2) } data = bytes.fromhex(hex_dump.replace("\n", "").replace(" ", "")) recovered_b32 = "" for i in range(0, len(data), 16): chunk = data[i:i+16] if chunk in rainbow_table: recovered_b32 += rainbow_table[chunk] clean_str = recovered_b32.rstrip('=') clean_str += '=' * ((8 - len(clean_str) % 8) % 8) print(base64.b32decode(clean_str).decode('utf-8')) ``` Flag: KCSC{3z_bru73_md5_4nd_d3cod3_b4s332} À còn description, chuyển từ hex sang thì được base64, tiếp tục base64 vài lần thì ra flag{thuuuuuuuuuuuuwwwwwwww vuuuuuuuuuuuu phuuuuuuuuuuuu chuuuuuuuuuuaaaaaaaa???????}... ![image](https://hackmd.io/_uploads/ByecKH2zbl.png) Vậy là phải bruteforce thật. 4.keyGenMe --- Description ``` The KCSC Software is protected by a username-dependent serial check. Combining static and dynamic analysis to bypass the protection and retrieve the flag. ``` ![image](https://hackmd.io/_uploads/rk24jBhfbe.png) Ok đây có vẻ là chương trình check license. Load vào ida, mình thực hiện tìm kiếm các chuỗi như trên hình thì tới được đây: <details> <summary>check lisence</summary> ```c= __int64 __fastcall sub_7FF6E3B622F2(HWND hWndParent, UINT a2, WPARAM a3, LPARAM a4) { HINSTANCE WindowLongPtrW; // rsi HWND Window; // rax HWND v9; // rax HWND v10; // rax HWND v11; // rbx HFONT FontW; // rax __int64 result; // rax HDC v14; // rbx HBRUSH v15; // r8 COLORREF v16; // r8d HPEN Pen; // rbp HGDIOBJ v18; // r12 HGDIOBJ StockObject; // rax HGDIOBJ v20; // rdi unsigned int v21; // eax CHAR String[256]; // [rsp+70h] [rbp-448h] BYREF __m128i Str; // [rsp+170h] [rbp-348h] BYREF _BYTE chText[280]; // [rsp+270h] [rbp-248h] BYREF char v25; // [rsp+388h] [rbp-130h] if ( a2 == 43 ) { result = 0LL; if ( *(_DWORD *)(a4 + 4) == 3 ) { v14 = *(HDC *)(a4 + 32); Str = _mm_loadu_si128((const __m128i *)(a4 + 40)); v15 = (HBRUSH)qword_7FF6E3B80F18; if ( dword_7FF6E3B80F08 ) v15 = hbr; FillRect(v14, (const RECT *)&Str, v15); v16 = 12156969; if ( dword_7FF6E3B80F08 ) v16 = 14391348; Pen = CreatePen(0, 1, v16); v18 = SelectObject(v14, Pen); StockObject = GetStockObject(5); SelectObject(v14, StockObject); RoundRect(v14, Str.m128i_i32[0], Str.m128i_i32[1], Str.m128i_i32[2], Str.m128i_i32[3], 5, 5); SelectObject(v14, v18); DeleteObject(Pen); GetWindowTextW(*(HWND *)(a4 + 24), (LPWSTR)chText, 256); SetBkMode(v14, 1); SetTextColor(v14, 0xFFFFFFu); v20 = SelectObject(v14, (HGDIOBJ)h); DrawTextW(v14, (LPCWSTR)chText, -1, (LPRECT)&Str, 0x25u); SelectObject(v14, v20); return 1LL; } } else if ( a2 > 0x2B ) { if ( a2 == 307 ) { SetTextColor((HDC)a3, 0x503E2Cu); SetBkColor((HDC)a3, 0xFFFFFFu); return (__int64)GetStockObject(0); } if ( a2 == 312 ) { SetTextColor((HDC)a3, 0x503E2Cu); SetBkColor((HDC)a3, 0xF5F0F0u); return (__int64)qword_7FF6E3B80F20; } if ( a2 != 273 ) return DefWindowProcW(hWndParent, a2, a3, a4); result = 0LL; if ( (_WORD)a3 == 3 && (a3 & 0xFFFF0000) == 0 ) { GetWindowTextA(qword_7FF6E3B80F50, String, 256); GetWindowTextA(qword_7FF6E3B80F48, Str.m128i_i8, 256); if ( String[0] && Str.m128i_i8[0] ) { if ( lpAddress && (unsigned int)((__int64 (__fastcall *)(CHAR *, __m128i *, void *))lpAddress)( String, &Str, &unk_7FF6E3B76520) == 1 ) { qmemcpy(chText, &unk_7FF6E3B76020, sizeof(chText)); v21 = strlen(Str.m128i_i8); sub_7FF6E3B621BD(&Str, v21, chText, 280LL); v25 = 0; sub_7FF6E3B753B0(Buffer, 0x400uLL, (wchar_t *)Format); sub_7FF6E3B617A6(hWndParent, Buffer); } else { sub_7FF6E3B62242(&unk_7FF6E3B761A0, &unk_7FF6E3B76140); sub_7FF6E3B753B0(Buffer, 0x400uLL, (wchar_t *)Format); sub_7FF6E3B617A6(hWndParent, Buffer); } return 0LL; } else { MessageBoxW(hWndParent, L"Please enter both Username and Serial Key!", "I", 0x30u); return 0LL; } } } else { if ( a2 != 2 ) { if ( a2 == 20 ) { GetClientRect(hWndParent, (LPRECT)chText); FillRect((HDC)a3, (const RECT *)chText, (HBRUSH)qword_7FF6E3B80F20); return 1LL; } if ( a2 == 1 ) { WindowLongPtrW = (HINSTANCE)GetWindowLongPtrW(hWndParent, -6); h = (WPARAM)CreateFontW(20, 0, 0, 0, 400, 0, 0, 0, 1u, 0, 0, 5u, 0, "S"); wParam = (WPARAM)CreateFontW(22, 0, 0, 0, 700, 0, 0, 0, 1u, 0, 0, 5u, 0, "S"); qword_7FF6E3B80F20 = CreateSolidBrush(0xF5F0F0u); qword_7FF6E3B80F18 = CreateSolidBrush(0xB98029u); hbr = CreateSolidBrush(0xDB9834u); Window = CreateWindowExW( 0, "S", L"KCSC Software Activation", 0x50000001u, 0, 15, 500, 30, hWndParent, 0LL, WindowLongPtrW, 0LL); SendMessageW(Window, 0x30u, wParam, 1LL); CreateWindowExW(0, "S", &word_7FF6E3B77222, 0x50000010u, 20, 55, 460, 2, hWndParent, 0LL, WindowLongPtrW, 0LL); v9 = CreateWindowExW(0, "S", L"Username:", 0x50000000u, 30, 80, 100, 20, hWndParent, 0LL, WindowLongPtrW, 0LL); SendMessageW(v9, 0x30u, h, 1LL); qword_7FF6E3B80F50 = CreateWindowExW( 0x200u, L"EDIT", &word_7FF6E3B77222, 0x50000080u, 140, 75, 320, 30, hWndParent, (HMENU)1, WindowLongPtrW, 0LL); SendMessageW(qword_7FF6E3B80F50, 0x30u, h, 1LL); v10 = CreateWindowExW( 0, "S", L"Serial Key:", 0x50000000u, 30, 125, 100, 20, hWndParent, 0LL, WindowLongPtrW, 0LL); SendMessageW(v10, 0x30u, h, 1LL); qword_7FF6E3B80F48 = CreateWindowExW( 0x200u, L"EDIT", &word_7FF6E3B77222, 0x50000080u, 140, 120, 320, 30, hWndParent, (HMENU)2, WindowLongPtrW, 0LL); SendMessageW(qword_7FF6E3B80F48, 0x30u, h, 1LL); qword_7FF6E3B80F40 = CreateWindowExW( 0, L"BUTTON", L"Activate License", 0x5000000Bu, 175, 170, 150, 40, hWndParent, (HMENU)3, WindowLongPtrW, 0LL); SendMessageW(qword_7FF6E3B80F40, 0x30u, h, 1LL); SetWindowSubclass(qword_7FF6E3B80F40, pfnSubclass, 0LL, 0LL); v11 = CreateWindowExW( 0, "S", L"Enter your username and serial key to activate", 0x50000001u, 0, 230, 500, 20, hWndParent, 0LL, WindowLongPtrW, 0LL); FontW = CreateFontW(16, 0, 0, 0, 400, 0, 0, 0, 1u, 0, 0, 5u, 0, "S"); SendMessageW(v11, 0x30u, (WPARAM)FontW, 1LL); return 0LL; } return DefWindowProcW(hWndParent, a2, a3, a4); } if ( lpAddress ) VirtualFree(lpAddress, 0LL, 0x8000u); if ( h ) DeleteObject((HGDIOBJ)h); if ( wParam ) DeleteObject((HGDIOBJ)wParam); if ( qword_7FF6E3B80F20 ) DeleteObject(qword_7FF6E3B80F20); if ( qword_7FF6E3B80F18 ) DeleteObject(qword_7FF6E3B80F18); if ( hbr ) DeleteObject(hbr); PostQuitMessage(0); return 0LL; } return result; } ``` </details> Có vẻ khá giống mấy bài crackme. Tại logic rõ trên màn hình và mình test phát được luôn nên mình lười đổi tên... Ở đây mình nên tìm tới hàm WindowProc vì nó quản lí sự kiện cửa sổ, như chương trình trên là DefWindowProcW. Hmm chưa thấy cái nào dị cho đến khi đập vào mắt mình là đoạn này: ![image](https://hackmd.io/_uploads/SJTsJ8hMbx.png) Hàm VirtualFree dùng để giải phóng vùng nhớ động được cấp phát, vậy có thể trước đó nó đã được cấp phát để chạy động trong RAM. ![image](https://hackmd.io/_uploads/rkN3lLnzWx.png) Đúng như mình nghĩ, lpAddress trống trơn. ![image](https://hackmd.io/_uploads/HJwgWLnGbe.png) Lần lên trên thì mình thấy đoạn check Thực hiện debug đặt bp tại if lpAddress trên x64dbg, với tham số lần lượt là RCX, RDX, R8: ![image](https://hackmd.io/_uploads/HJDAZI2zWl.png) Đúng như mình dự đoán, lpAddress nhận tham số là name và serial, tiếp tục step into (f7) vào hàm. ![image](https://hackmd.io/_uploads/HJjk782MZe.png) ![image](https://hackmd.io/_uploads/ry6jGU2fbe.png) <details> <summary>check license (lpAddress)</summary> ```asm= 000001A99D1C0000 | test rdx,rdx | rdx:"dua_flag_cho_em_huhu" 000001A99D1C0003 | push rbp | 000001A99D1C0004 | sete al | 000001A99D1C0007 | test r8,r8 | 000001A99D1C000A | push rdi | 000001A99D1C000B | push rsi | rsi:GetWindowTextA 000001A99D1C000C | push rbx | 000001A99D1C000D | mov rbx,rdx | rdx:"dua_flag_cho_em_huhu" 000001A99D1C0010 | sete dl | 000001A99D1C0013 | or al,dl | 000001A99D1C0015 | jne 1A99D1C0108 | 000001A99D1C001B | xor eax,eax | 000001A99D1C001D | test rcx,rcx | rcx:"Le_Trong_Duc" 000001A99D1C0020 | je 1A99D1C0108 | 000001A99D1C0026 | cmp byte ptr ds:[rcx+rax],0 | 000001A99D1C002A | je 1A99D1C0037 | 000001A99D1C002C | inc rax | 000001A99D1C002F | cmp rax,100 | 000001A99D1C0035 | jne 1A99D1C0026 | 000001A99D1C0037 | xor edx,edx | 000001A99D1C0039 | cmp byte ptr ds:[rbx+rdx],0 | 000001A99D1C003D | je 1A99D1C0053 | 000001A99D1C003F | inc rdx | rdx:"dua_flag_cho_em_huhu" 000001A99D1C0042 | cmp rdx,100 | rdx:"dua_flag_cho_em_huhu" 000001A99D1C0049 | jne 1A99D1C0039 | 000001A99D1C004B | mov r11d,100 | 000001A99D1C0051 | jmp 1A99D1C0056 | 000001A99D1C0053 | mov r11d,edx | 000001A99D1C0056 | test r11d,r11d | 000001A99D1C0059 | je 1A99D1C0108 | 000001A99D1C005F | cmp eax,C | 0C:'\f' 000001A99D1C0062 | jne 1A99D1C0108 | 000001A99D1C0068 | xor eax,eax | 000001A99D1C006A | cmp byte ptr ds:[rcx],6B | rcx:"Le_Trong_Duc", 6B:'k' 000001A99D1C006D | jne 1A99D1C010A | 000001A99D1C0073 | cmp byte ptr ds:[rcx+2],61 | rcx+02:"_Trong_Duc", 61:'a' 000001A99D1C0077 | jne 1A99D1C010A | 000001A99D1C007D | cmp byte ptr ds:[rcx+4],33 | rcx+04:"rong_Duc", 33:'3' 000001A99D1C0081 | jne 1A99D1C010A | 000001A99D1C0087 | cmp byte ptr ds:[rcx+6],33 | rcx+06:"ng_Duc", 33:'3' 000001A99D1C008B | jne 1A99D1C010A | 000001A99D1C008D | cmp byte ptr ds:[rcx+8],35 | rcx+08:"_Duc", 35:'5' 000001A99D1C0091 | jne 1A99D1C010A | 000001A99D1C0093 | cmp byte ptr ds:[rcx+A],6E | rcx+0A:"uc", 6E:'n' 000001A99D1C0097 | jne 1A99D1C010A | 000001A99D1C0099 | cmp byte ptr ds:[rcx+1],6D | rcx+01:"e_Trong_Duc", 6D:'m' 000001A99D1C009D | jne 1A99D1C010A | 000001A99D1C009F | cmp byte ptr ds:[rcx+3],72 | rcx+03:"Trong_Duc", 72:'r' 000001A99D1C00A3 | jne 1A99D1C010A | 000001A99D1C00A5 | cmp byte ptr ds:[rcx+5],76 | rcx+05:"ong_Duc", 76:'v' 000001A99D1C00A9 | jne 1A99D1C010A | 000001A99D1C00AB | cmp byte ptr ds:[rcx+7],72 | rcx+07:"g_Duc", 72:'r' 000001A99D1C00AF | jne 1A99D1C010A | 000001A99D1C00B1 | cmp byte ptr ds:[rcx+9],21 | rcx+09:"Duc", 21:'!' 000001A99D1C00B5 | jne 1A99D1C010A | 000001A99D1C00B7 | cmp byte ptr ds:[rcx+B],39 | 39:'9' 000001A99D1C00BB | jne 1A99D1C010A | 000001A99D1C00BD | xor r10d,r10d | 000001A99D1C00C0 | mov r9d,1337 | 000001A99D1C00C6 | mov esi,C | 0C:'\f' 000001A99D1C00CB | mov eax,r10d | 000001A99D1C00CE | movsx ebp,byte ptr ds:[rbx+r10] | rbx+r10*1:SetErrorInfo+2946 000001A99D1C00D3 | cdq | 000001A99D1C00D4 | idiv esi | 000001A99D1C00D6 | mov eax,r9d | 000001A99D1C00D9 | xor ebp,r9d | 000001A99D1C00DC | movsxd rdx,edx | rdx:"dua_flag_cho_em_huhu" 000001A99D1C00DF | movzx edi,byte ptr ds:[rcx+rdx] | 000001A99D1C00E3 | xor edx,edx | 000001A99D1C00E5 | div esi | 000001A99D1C00E7 | xor edi,ebp | 000001A99D1C00E9 | mov edx,edx | 000001A99D1C00EB | movzx r9d,byte ptr ds:[rcx+rdx] | 000001A99D1C00F0 | add r9d,edi | 000001A99D1C00F3 | cmp dword ptr ds:[r8+r10*4],r9d | 000001A99D1C00F7 | jne 1A99D1C0108 | 000001A99D1C00F9 | inc r10 | r10:NtUserMessageCall+14 000001A99D1C00FC | cmp r11d,r10d | 000001A99D1C00FF | jg 1A99D1C00CB | 000001A99D1C0101 | mov eax,1 | 000001A99D1C0106 | jmp 1A99D1C010A | 000001A99D1C0108 | xor eax,eax | 000001A99D1C010A | pop rbx | 000001A99D1C010B | pop rsi | rsi:GetWindowTextA 000001A99D1C010C | pop rdi | 000001A99D1C010D | pop rbp | 000001A99D1C010E | ret | ``` </details> RCX mang name, RDX mang giá trị serial. ![image](https://hackmd.io/_uploads/S1WwSUnM-l.png) RAX biến đếm, RCX con trỏ tới chuỗi name, `cmp byte ptr ds:[rcx+rax],0` nếu kí tự đang check bằng 0 (kí tự xuống dòng \n) thì nhảy khỏi vòng lặp => Đoạn này tính độ dài kí tự, tối đa là 100. Được lưu vào RAX. Đoạn này cũng tương tự nhưng là độ dài serial: ![image](https://hackmd.io/_uploads/HJBAUIhGWg.png) Độ dài được lưu vào RDX. ![image](https://hackmd.io/_uploads/B1Z5wIhG-e.png) So sánh độ dài name với 12, vậy length name phải bằng 12 kí tự. ```asm= 000001A99D1C006A | cmp byte ptr ds:[rcx],6B | rcx:"Le_Trong_Duc", 6B:'k' 000001A99D1C006D | jne 1A99D1C010A | 000001A99D1C0073 | cmp byte ptr ds:[rcx+2],61 | rcx+02:"_Trong_Duc", 61:'a' 000001A99D1C0077 | jne 1A99D1C010A | 000001A99D1C007D | cmp byte ptr ds:[rcx+4],33 | rcx+04:"rong_Duc", 33:'3' 000001A99D1C0081 | jne 1A99D1C010A | 000001A99D1C0087 | cmp byte ptr ds:[rcx+6],33 | rcx+06:"ng_Duc", 33:'3' 000001A99D1C008B | jne 1A99D1C010A | 000001A99D1C008D | cmp byte ptr ds:[rcx+8],35 | rcx+08:"_Duc", 35:'5' 000001A99D1C0091 | jne 1A99D1C010A | 000001A99D1C0093 | cmp byte ptr ds:[rcx+A],6E | rcx+0A:"uc", 6E:'n' 000001A99D1C0097 | jne 1A99D1C010A | 000001A99D1C0099 | cmp byte ptr ds:[rcx+1],6D | rcx+01:"e_Trong_Duc", 6D:'m' 000001A99D1C009D | jne 1A99D1C010A | 000001A99D1C009F | cmp byte ptr ds:[rcx+3],72 | rcx+03:"Trong_Duc", 72:'r' 000001A99D1C00A3 | jne 1A99D1C010A | 000001A99D1C00A5 | cmp byte ptr ds:[rcx+5],76 | rcx+05:"ong_Duc", 76:'v' 000001A99D1C00A9 | jne 1A99D1C010A | 000001A99D1C00AB | cmp byte ptr ds:[rcx+7],72 | rcx+07:"g_Duc", 72:'r' 000001A99D1C00AF | jne 1A99D1C010A | 000001A99D1C00B1 | cmp byte ptr ds:[rcx+9],21 | rcx+09:"Duc", 21:'!' 000001A99D1C00B5 | jne 1A99D1C010A | 000001A99D1C00B7 | cmp byte ptr ds:[rcx+B],39 | 39:'9' ``` Đoạn này so sánh từng byte name với chuỗi cứng. Nếu không bằng thì nhảy tới 1A99D1C010A (kết thúc hàm). Giờ lần theo từng cái rcx+index là ra name chuẩn: `kmar3v3r5!n9` Ok vậy đoạn code còn lại là check serial. Khởi tạo `r10d`, đưa 0x1337 vào `r9d`, `esi` là C (12). `movsx ebp,byte ptr ds:[rbx+r10]` Đầu hàm có lệnh mov rbx, rdx (rdx: serial) => đoạn này là chuyển serial[index] vào ebp (có mở rộng dấu âm dương tùy trường hợp) ![image](https://hackmd.io/_uploads/rJI13IhfZx.png) Chia lấy dư cho 12 (edx sẽ lưu phần dư). ![image](https://hackmd.io/_uploads/BkhAh83G-g.png) Copy 0x1337 ra Rax và xor serial[index] với 0x1337 ![image](https://hackmd.io/_uploads/HJrhTUhzbe.png) lấy từng byte name + rdx (số dư chia 12 hồi nãy) đem qua edi, chia eax cho esi (là 0x1337 cho C (12)) Mà sau đó không thấy đụng đến eax nữa? vậy chắc là để lấy số dư vì rdx lưu số dư sau phép chia. ebp chứa serial[index] vừa nãy được xor với r9d (0x1337), còn edi là kết quả ở địa chỉ 0xDF. ![image](https://hackmd.io/_uploads/ryCQWPnfWe.png) Tại EB, đưa byte ptr rcx+rdx vào r9d, vậy giờ r9d không còn mang 0x1337 mà là kí tự nào đó trong chuỗi kmar3v3r5!n9 (dựa vào phép chia dư hồi nãy giờ rdx mang, cụ thể thì idk). Sau đó cộng r9d với edi. ![image](https://hackmd.io/_uploads/SJ_azwnzWl.png) So sánh với r8, r10 (index nãy giờ), r9d (kết quả trên). Vậy chắc r8 sẽ giữ địa chỉ gì đó đến chỗ quan trọng. Thực hiện dump memory giá trị r8 đang giữ. ``` 00007FF6D0FA6520 50 13 00 00 E9 13 00 00 FC 13 00 00 00 14 00 00 P...é...ü....... 00007FF6D0FA6530 53 14 00 00 89 14 00 00 F7 14 00 00 29 15 00 00 S.......÷...)... 00007FF6D0FA6540 A0 15 00 00 DF 15 00 00 6D 16 00 00 77 16 00 00  ...ß...m...w... 00007FF6D0FA6550 9B 16 00 00 33 17 00 00 B8 17 00 00 03 18 00 00 ....3...¸....... 00007FF6D0FA6560 75 18 00 00 58 18 00 00 8D 18 00 00 F3 18 00 00 u...X.......ó... 00007FF6D0FA6570 05 19 00 00 9D 19 00 00 39 1A 00 00 C8 1A 00 00 ........9...È... 00007FF6D0FA6580 C1 1A 00 00 B9 1A 00 00 23 1B 00 00 6E 1B 00 00 Á...¹...#...n... 00007FF6D0FA6590 9A 1B 00 00 2F 1C 00 00 BB 1C 00 00 36 1D 00 00 ..../...»...6... 00007FF6D0FA65A0 A8 1D 00 00 02 1E 00 00 A2 1E 00 00 FF 1E 00 00 ¨.......¢...ÿ... 00007FF6D0FA65B0 27 1F 00 00 90 1F 00 00 EC 1F 00 00 1E 20 00 00 '.......ì.... .. 00007FF6D0FA65C0 D9 20 00 00 BB 20 00 00 2D 21 00 00 2E 21 00 00 Ù ..» ..-!...!.. 00007FF6D0FA65D0 A4 21 00 00 00 22 00 00 8D 22 00 00 66 23 00 00 ¤!..."..."..f#.. 00007FF6D0FA65E0 9F 23 00 00 10 01 00 00 00 00 00 00 00 00 00 00 .#.............. 00007FF6D0FA65F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ``` Nếu khác thì nhảy xuống cuối hàm. Không thì chạy tiếp vòng lặp ![image](https://hackmd.io/_uploads/ByLLNw2z-l.png) Script giải mã: ```python= username = "kmar3v3r5!n9" target_table = [ 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 ] serial_key = "" current_xor_key = 0x1337 print(f"key: {hex(current_xor_key)}") for i in range(len(target_table)): magic_index = current_xor_key % 12 magic_char = ord(username[magic_index]) target_val = target_table[i] temp = target_val - magic_char temp = temp ^ current_xor_key user_char_at_i = ord(username[i % 12]) decoded_char_code = temp ^ user_char_at_i serial_key += chr(decoded_char_code) current_xor_key = target_val print(serial_key) ``` ![image](https://hackmd.io/_uploads/HyXS8PhGbg.png) ![image](https://hackmd.io/_uploads/S1n_8vhM-e.png) Đem lên cyberchef và base64 vài lần là ra flag ``` KCSC{C0n9r4tu14t!0n5_Y0u_H4v3_Succ355fu11y_4ct!v4t4t3d_7h3_L!c3n53_S0ftw4r3_W!th_RC4_4nd_D3c0d3_Base64_3_T!m3s__=)))} ``` 5.medium antidebug --- Vì tên bài spoil antidebug nên mình chạy đi kiếm antidebug thay vì main như mọi khi, cam on a vi dat ten rat hay 🪿 Bài này áp dụng khá nhiều kĩ thuật antidebug: EXCEPTION_RECORD, ContextRecord, AddVectoredExceptionHandler Có thể tham khảo thêm [tại đây](https://hackmd.io/@Zupp/Task2#Task-2-Anti-debug)(Mục 3.3 Control Flow Hiding), [tại đây](https://anti-debug.checkpoint.com/techniques/exceptions.html#hiding-cf-with-eh) (Hiding Control Flow with Exception Handlers), [tại đây](https://learn.microsoft.com/en-us/windows/win32/debug/vectored-exception-handling). ![image](https://hackmd.io/_uploads/ryHuFdhzZl.png) Tại tlscallback0 thấy có gọi api khá sú: `AddVectoredExceptionHandler` Đây là kĩ thuật antidebug sử dụng veh để xử lí các ngoại lệ. ![image](https://hackmd.io/_uploads/SyoCtO2fZx.png) Hàm veh này được gọi mỗi khi có byte CC (int3), thay vì sửa lỗi thì nó tráo đổi mã tới hàm cần sử dụng. Quay lại hàm main: <details> <summary>main</summary> ```c= 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_CB43D8 = IsDebuggerPresent(); __debugbreak(); dword_CB43D0 = IsDebuggerPresent(); __debugbreak(); v11 = dword_CB43D8; IsDebuggerPresent(); __debugbreak(); v10 = dword_CB43D8; IsDebuggerPresent(); __debugbreak(); v9 = dword_CB43D0; IsDebuggerPresent(); v3 = dword_CB43E0; if ( dword_CB43E0 ) { if ( byte_CB449F[dword_CB43E0] == 10 ) v3 = --dword_CB43E0; if ( v3 && byte_CB449F[v3] == 13 ) dword_CB43E0 = --v3; } if ( v3 >= 0x40 ) __report_rangecheckfailure( v9, byte_CB44A0, 64, &dword_CB43E0, 0, v10, "Enter flag: ", 12, &unk_CB43DC, 0, v11, "Welcome to KCSC!\n", 17, &unk_CB43DC, 0, -10, -11); byte_CB44A0[v3] = 0; __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); dword_CB43E8 = (dword_CB43E0 + 7) & 0xFFFFFFF8; __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); __debugbreak(); IsDebuggerPresent(); dword_CB43CC = dword_CB43E8; if ( dword_CB43E8 == 40 ) { v4 = 1; dword_CB43EC = 0; v5 = 0; v6 = 0; v7 = 0; while ( input_buffer[v7] == byte_CB3184[v6] ) { v6 = ++v5; dword_CB43EC = 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; } ``` </details> Hàm main cố tình gây exception để kích hoạt hàm veh, từ đó tráo đổi thành các hàm thật sự chạy. ``` __debugbreak(); v10 = dword_CB43D8; IsDebuggerPresent(); ``` Quay lại với veh: ![image](https://hackmd.io/_uploads/SJYhod2Mbe.png) veh phải gọi unhook_api trước để xóa sạch mấy hàm api cũ đã lưu lần trước bằng IsDebuggerPresent. <details> <summary>unhook_api()</summary> ```c= void unhook_api() { HANDLE CurrentProcess; // eax DWORD flOldProtect; // [esp+0h] [ebp-8h] BYREF if ( !dword_CB4058 ) { VirtualProtect(IsDebuggerPresent, 5u, 0x40u, &flOldProtect); IsDebuggerPresent = (BOOL (__stdcall *)())dword_CB4480; *((_BYTE *)&IsDebuggerPresent + 4) = byte_CB4484; VirtualProtect(IsDebuggerPresent, 5u, flOldProtect, &flOldProtect); CurrentProcess = GetCurrentProcess(); FlushInstructionCache(CurrentProcess, IsDebuggerPresent, 5u); } } ``` </details> Sau đó gọi hook_api để gọi hàm (ghi đè lệnh jmp) ![image](https://hackmd.io/_uploads/BJwRaunM-x.png) Đây là các hàm được gọi. Có 3 hàm giữa sú, 2 trong số đó bị lặp lại. GetStdHandle, WriteConsoleA, ReadConsoleA được sử dụng để in ra chuỗi welcome, enter flag,... và nhận dữ liệu từ người dùng. ![image](https://hackmd.io/_uploads/ByiXJY3fZl.png) <details> <summary>RC4_ksa()</summary> ```c= char __cdecl RC4_ksa(int sbox, int key, int key_len) { int v3; // esi int v4; // eax int i; // edi char v6; // bl char result; // al v3 = sbox; v4 = 0; LOBYTE(sbox) = 0; do { *(_BYTE *)(v3 + v4) = v4; ++v4; } while ( v4 < 256 ); for ( i = 0; i < 256; ++i ) { v6 = *(_BYTE *)(v3 + i); sbox = (unsigned __int8)(sbox + *(_BYTE *)(i % key_len + key) + v6); result = *(_BYTE *)(sbox + v3); *(_BYTE *)(v3 + i) = result; *(_BYTE *)(sbox + v3) = v6; } *(_DWORD *)(v3 + 256) = 0; *(_DWORD *)(v3 + 260) = 0; return result; } ``` </details> - Khởi tạo sbox - Trộn Sbox dựa trên key Đặt bp tại hàm này để lấy key, tham số thứ 2 truyền vào stack. <details> <summary>RC4_prga()</summary> ```c= void __cdecl RC4_prga(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 ); } } ``` </details> <details> <summary>TEA_encrypt()</summary> ```c= int __cdecl TEA_encrypt(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; } ``` </details> - -1640531527 là 0x9E3779B9 (hằng số trong TEA) - Vòng lặp 32 lần (32 round) - a4 là key? - `v7 += (v9 + v8) ^ (*a4 + 16 * v8) ^ (a4[1] + (v8 >> 5))` đối chiếu với công thức chuẩn của TEA: `v0 += ((v1<<4) + k0) ^ (v1+sum) ^ ((v1>>5) + k1)` ta thấy v1 << 4 tương đương với *16, *a4 được cộng và dịch về trái 16 bit, a4[1] cộng dịch vế phải. nên a4 là k (aka key). Tí đặt bp ở hàm tea này để săn tham số thứ 4 (a4). Thứ tự các hàm được gọi: - Nhập input - Gọi RC4_ksa và RC4_prga mã hóa - Mã hóa kết quả trên bằng TEA_encrypt - Gọi tiếp 2 hàm RC4 trên để mã hóa kết quả của TEA. - So sánh với ciphertext trong memory. Vậy giờ ta cần key ở RC4_ksa, TEA_encrypt, và ciphertext trong memory (RC4_ksa được gọi 2 lần nên ta phải lấy cả 2 lần). 1. Cyphertext (main function) ![image](https://hackmd.io/_uploads/r1fi4Y3M-g.png) Trong main có đoạn thực hiện so sánh từng byte input_buffer được đẩy vào stack trước khi hook api nên khá chắc đây là con trỏ đến địa chỉ chuỗi nhập vào. ![image](https://hackmd.io/_uploads/rkwDNthMZl.png) Vậy thì byte_CB3184 có thể là cyphertext mà ta cần tìm trong memory, sử dụng x32dbg để debug và đặt bp. Thiết lập exception CC (mã của nó là 80000003) để không break và cho chương trình xử lí thay vì debugger là ta đã có thể bypass hầu như antidebug trong bài... (sống chung với lũ) ![image](https://hackmd.io/_uploads/BJLiBK2G-e.png) À trước đó thì chương trình còn có phần so sánh độ dài nữa ![image](https://hackmd.io/_uploads/ryPnUF2z-e.png) Nếu input có 40 kí tự thì mới nhảy vào so sánh. ![image](https://hackmd.io/_uploads/HJAePY3MWl.png) ![image](https://hackmd.io/_uploads/BkpNPK2z-e.png) Ta thực hiện dump 40 byte từ vùng nhớ này 2. RC4_ksa Đặt hwbp tại 00981030 địa chỉ của hàm RSA_ksa ![image](https://hackmd.io/_uploads/HJ7TTY2Mbl.png) ![image](https://hackmd.io/_uploads/H15lAK3GZl.png) Tham số thứ 2 trên stack -> follow in dump. `1A 89 14 92 22 5D 4F` RC4_ksa lần gọi 2: `36 BD D1 3B 9A A8 EF 29 FF` 3. TEA_encrypt Đặt hwbp tại 00981120 địa chỉ ở hàm TEA_encrypt ![image](https://hackmd.io/_uploads/ByXORK3M-l.png) ![image](https://hackmd.io/_uploads/S14qCt3Mbl.png) Dump memory ở địa chỉ tham số thứ 4. ![image](https://hackmd.io/_uploads/ByAh0YnM-e.png) `EF BE AD DE BE BA FE CA 37 13 37 13 0D F0 AD BA` Script giải mã (powered by AI): ```python= import struct ct = bytes.fromhex("7B6FB4F7813DEFEEB21E7E8D0C9D6741CB2053FD4771ADC6BC0EF2A26904A3B25ED7D06E636F729E") k_inner = bytes.fromhex("1A891492225D4F") k_outer = bytes.fromhex("36BDD13B9AA8EF29FF") k_tea = struct.unpack("<4I", bytes.fromhex("EFBEADDEBEBAFECA371337130DF0ADBA")) def rc4(data, key): S, j, out = list(range(256)), 0, bytearray() for i in range(256): j = (j + S[i] + key[i % len(key)]) % 256 S[i], S[j] = S[j], S[i] i = j = 0 for b in data: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] out.append(b ^ S[(S[i] + S[j]) % 256]) return out def decrypt_tea(v, k): v0, v1 = v delta, mask, sum_val = 0x9E3779B9, 0xFFFFFFFF, 0xC6EF3720 for _ in range(32): v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + sum_val) ^ ((v0 >> 5) + k[3]))) & mask v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + sum_val) ^ ((v1 >> 5) + k[1]))) & mask sum_val = (sum_val - delta) & mask return v0, v1 ct = rc4(ct, k_outer) ct_tea = b"" for i in range(0, len(ct), 8): block = struct.unpack("<2I", ct[i:i+8]) ct_tea += struct.pack("<2I", *decrypt_tea(block, k_tea)) flag = rc4(ct_tea, k_inner) print(flag) ``` Flag: `KCSC{now_you_know_how_to_hook_WINAPI}` 6.(33,550,337) --- .apk file Description: ``` All shall bid farewell to one, and that person alone will witness the miracle. ``` Đây là lần đầu tiên mình thực hiện trên file apk. Chưa biết gì nên tra mạng đọc tài liệu. Giờ thì tải Jadx về vọc vạch với file apk đó. Mò mỏi tay thì không thấy gì, trong MainActivity không thấy, resource cũng không, mò tiếp thì thấy kiểu như "io.flutter", hỏi AI thì chương trình này được viết bằng flutter, mã nguồn java là cái vỏ, logic chính nằm trong file libapp.so trong lib/x86_64/ . Trước tiên chạy thử chương trình trên máy ảo android trước (mình sử dụng android studio). Có thể cài lên máy android thật bằng tool adb nhưng mình sài iphone... ![image](https://hackmd.io/_uploads/S1AFRw3fZe.png) Đây là dạng bài click đến mỏi tay, 1 giây mình click được 10 lần thì phải mất 40 ngày mới có flag 🐔, vậy nên sẽ thực hiện patch file apk. Thực chất file apk cũng giống file zip nên mình thực hiện giải nén nó. Load vào ida phân tích file .so, thực hiện tìm kiếm giá trị 33,550,337 (hex là 1FFF001). ![image](https://hackmd.io/_uploads/r11I6w2GZl.png) Cmp thẳng với giá trị 1FFF001 nên mình sẽ nop lệnh jump ngay sau đó. Sau đó Edit -> Patch program -> Apply patch to... ![image](https://hackmd.io/_uploads/ryA9yO3G-x.png) Sau đó zip file lại với compression level là store (không nén), sau đó đổi đuôi.zip thành .apk. Trước kia thì android cũ sẽ lôi file .so từ trong apk ra, giải nén và sử dụng. Nhưng phiên bản android hiện tại thì lôi file .so ra Ram sử dụng luôn thay vì nén, nên file .so phải ở dạng không nén. Khi thay đổi file apk thì chữ kí số sẽ bị thay đổi, mình cần sử dụng tool để ký lại, tool mình sử dụng là [uber-apk-signer](https://github.com/patrickfav/uber-apk-signer) ![image](https://hackmd.io/_uploads/H1gSMOnf-e.png) ![image](https://hackmd.io/_uploads/B1eLGOhG-l.png) `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}` ###### tags: `Rev` `Wu`