## I. Introduction
- Ngay khi có ý định writeup lại các challenge của `kcbowhunter`, mình đã khá do dự, vì lí do này:

- RE là một mảng khó, và rất thú vị, mình nghĩ nên tôn trọng quan điểm, công sức của tác giả khi tạo ra các chall này. Vì vậy, thay vì `*R chall`, mình sẽ (cố gắng) clear toàn bộ chall của đợt TTV 2024 tháng 9 vừa qua. Các chall mình sẽ sắp xếp theo thứ tự tăng dần độ khó, và chỉ riêng bài của anh Khiêm mặc dù `Easy` nhưng là `Troller` thui :angry:.
## II. Writeup
### 1. riel warmup (Warmup - 13r_ə_Rɪst)
- Bài này thì thực sự mình cũng không biết nói gì hơn :v. Giống với warmup của năm ngoái, bài rev đúng phong cách mở ida +1 flag luôn. Đây là hàm `main`:

và đây là hàm `hide`:

- Dễ thấy flag thôi:
> ~~`KCSC{have_u_known_how_to_use_IDA_after_this_real_warmup}`~~
- Note: cá nhân mình thấy chall khá thú vị nhưng chỉ dừng ở mức `Super beginner` thui, dùng để giải trí lúc stuck chall `Hard` là hợp lí.
### 2. Time travel (Easy - 13r_ə_Rɪst)
- Do file enc của author đã được sửa trên web nên mình sẽ up lại ở đây cho các bạn dễ làm lại nếu không có:
```
b'hh}\xc67\x8e\xfd\xeb4\xfe\x17\xfe\x9d(\xe0\x04p\x85\xb7Dh7\xc0\xfb"\xc9\xa0I\xbd*\xb6\xcf\xb8E\xbdDP\x87\xb1H\xf3\xc9|#\xb8\xf9.\x83\xbd\xee\xa8\xd9\x08\xef7\x98\x1cv\xc5\x13\xf6\xf0\xdc\x0e*Q\xbd\xd4#d\x84S\xe1K'
```
- Mình không khuyến khích các bạn run luôn `chall.exe` nhé :v. Bài có idea khá hay, mỗi lần ~~lỡ tay~~ bấm vào run file sẽ là một lần stuck vì chall thực hiện mã hóa động luôn. Với bài này thì mình không tìm anti-debug vì mình phân tích tĩnh:

- Mình đã đổi tên hàm `genSeed` ở kia, trên thực tế hàm call tới `time64` tạo seed rồi thực thi `srand(seed)`. Về cách sử dụng thời gian để gen seed thì các bạn sẽ gặp rất nhiều, chall Crypto của anh Jella năm 2023 cũng có một bài dùng seed rất hay.
- Kế đó, chương trình thực hiện ghi file `enc.txt` và mã hóa flag:

- Bài tạo ra các byte rác bằng `rand()`, nên nhớ là chương trình sử dụng các hàm lấy từ thư viện của C, nên code giải cũng phải dùng C hoặc ctypes trong python nhé. Mình đã comment rất chi tiết trong bài rùi, các bạn có thể tham khảo nhé.
- Từ đây mình thấy có thể vũ phu lại seed này, tuy nhiên cần phải thử cả hai trường hợp, đầu tiên seed chạy từ 0, thứ hai là seed rất gần ngày tạo chall (`Tung quen`). Đây là seed theo trường hợp đầu tiên, mình sẽ cho `srand(seed)` rồi `xor` 5 kí tự đầu tiên của `enc` với `rand()` và `and 0xFF` kiểm tra nó có phải format của flag không. Nếu đúng thì sẽ lấy được cả flag thui:
```c=
#include <bits/stdc++.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
int main(){
int enc[74] =
{
0x68, 0x68, 0x7d, 0xc6, 0x37, 0x8e, 0xfd, 0xeb, 0x34, 0xfe,
0x17, 0xfe, 0x9d, 0x28, 0xe0, 0x04, 0x70, 0x85, 0xb7, 0x44,
0x68, 0x37, 0xc0, 0xfb, 0x22, 0xc9, 0xa0, 0x49, 0xbd, 0x2a,
0xb6, 0xcf, 0xb8, 0x45, 0xbd, 0x44, 0x50, 0x87, 0xb1, 0x48,
0xf3, 0xc9, 0x7c, 0x23, 0xb8, 0xf9, 0x2e, 0x83, 0xbd, 0xee,
0xa8, 0xd9, 0x08, 0xef, 0x37, 0x98, 0x1c, 0x76, 0xc5, 0x13,
0xf6, 0xf0, 0xdc, 0x0e, 0x2a, 0x51, 0xbd, 0xd4, 0x23, 0x64,
0x84, 0x53, 0xe1, 0x4b
};
int check[5];
int found = 0;
long long start_time = 0;
long long end_time = 1 << 26;
time_t epoch1 = start_time;
time_t epoch2 = end_time;
printf("%s", asctime(gmtime(&epoch1)));
printf("%s", asctime(gmtime(&epoch2)));
for(long long i=start_time; i<=end_time; i++){
srand(i);
for(int j=0; j<5; j++) check[j] = (enc[j] ^ rand()) & 0xFF;
if(check[0] == 75 && check[1] == 67 && check[2] == 83 && check[3] == 67 && check[4] == 123){
found = 1;
printf("Found at %s\n", asctime(gmtime(&i)));
srand(i);
for(int k=0; k<74; k++){
printf("%c", (enc[k] ^ rand()) & 0xFF);
}
return 0;
}
}
if(!found) printf("Not found");
return 0;
}
```
- Và kết quả:

> ~~`KCSC{0xffffff_is_1970-07-14,I_created_this_challenge_at_"the_end"_of_time}`~~
- Note, một bài khá hay và khá vui, nhưng mình đã phải hỏi tác giả để định hình lại mindset và cases của seed kia :v, ngốc nghếch thật sự.
### 3. Elif (Easy - noobmannn)
- Source chúng ta có ở đây là một kiểu flag checker khá điển hình (cho một bài reverse dễ) vì có các phương trình và cho biết khá nhiều thông tin:
```python=
flag = input('Enter your flag: ')
if len(flag) != 49:
print('Wrong Length!!!')
exit()
inp = [ord(c) for c in flag]
if inp[30] + inp[44] + inp[16] + inp[38] + inp[47] + inp[7] != 398:
print('Wrong Flag!!!')
exit()
elif inp[41] + inp[22] + inp[38] + inp[33] + inp[28] + inp[20] != 451:
print('Wrong Flag!!!')
exit()
elif inp[10] + inp[3] + inp[39] + inp[14] + inp[4] + inp[47] != 440:
print('Wrong Flag!!!')
exit()
elif inp[2] + inp[12] + inp[45] + inp[4] + inp[42] + inp[30] != 581:
print('Wrong Flag!!!')
exit()
elif inp[36] + inp[36] + inp[26] + inp[43] + inp[21] + inp[1] != 587:
print('Wrong Flag!!!')
exit()
elif inp[16] + inp[3] + inp[16] + inp[20] + inp[38] + inp[39] != 274:
print('Wrong Flag!!!')
exit()
elif inp[28] + inp[39] + inp[18] + inp[38] + inp[47] + inp[8] != 372:
print('Wrong Flag!!!')
exit()
elif inp[25] + inp[19] + inp[36] + inp[19] + inp[20] + inp[31] != 470:
print('Wrong Flag!!!')
exit()
elif inp[44] + inp[27] + inp[5] + inp[41] + inp[16] + inp[42] != 565:
print('Wrong Flag!!!')
exit()
elif inp[46] + inp[35] + inp[8] + inp[1] + inp[4] + inp[47] != 447:
print('Wrong Flag!!!')
exit()
elif inp[41] + inp[20] + inp[42] + inp[40] + inp[3] + inp[43] != 503:
print('Wrong Flag!!!')
exit()
elif inp[36] + inp[4] + inp[21] + inp[46] + inp[34] + inp[38] != 532:
print('Wrong Flag!!!')
exit()
elif inp[43] + inp[45] + inp[3] + inp[45] + inp[3] + inp[17] != 382:
print('Wrong Flag!!!')
exit()
elif inp[24] + inp[2] + inp[6] + inp[2] + inp[25] + inp[1] != 490:
print('Wrong Flag!!!')
exit()
elif inp[38] + inp[41] + inp[33] + inp[34] + inp[21] + inp[42] != 569:
print('Wrong Flag!!!')
exit()
elif inp[17] + inp[38] + inp[1] + inp[15] + inp[46] + inp[35] != 364:
print('Wrong Flag!!!')
exit()
elif inp[40] + inp[17] + inp[34] + inp[33] + inp[39] + inp[19] != 398:
print('Wrong Flag!!!')
exit()
elif inp[18] + inp[21] + inp[4] + inp[27] + inp[19] + inp[29] != 541:
print('Wrong Flag!!!')
exit()
elif inp[30] + inp[34] + inp[42] + inp[26] + inp[18] + inp[47] != 588:
print('Wrong Flag!!!')
exit()
elif inp[23] + inp[24] + inp[30] + inp[1] + inp[13] + inp[7] != 471:
print('Wrong Flag!!!')
exit()
elif inp[17] + inp[16] + inp[32] + inp[16] + inp[15] + inp[14] != 343:
print('Wrong Flag!!!')
exit()
elif inp[30] + inp[10] + inp[24] + inp[3] + inp[40] + inp[3] != 519:
print('Wrong Flag!!!')
exit()
elif inp[10] + inp[34] + inp[27] + inp[38] + inp[46] + inp[40] != 480:
print('Wrong Flag!!!')
exit()
elif inp[6] + inp[6] + inp[46] + inp[35] + inp[5] + inp[13] != 357:
print('Wrong Flag!!!')
exit()
elif inp[18] + inp[16] + inp[5] + inp[6] + inp[12] + inp[32] != 411:
print('Wrong Flag!!!')
exit()
elif inp[1] + inp[3] + inp[37] + inp[4] + inp[22] + inp[44] != 514:
print('Wrong Flag!!!')
exit()
elif inp[26] + inp[11] + inp[12] + inp[47] + inp[22] + inp[2] != 541:
print('Wrong Flag!!!')
exit()
elif inp[32] + inp[32] + inp[18] + inp[34] + inp[31] + inp[37] != 454:
print('Wrong Flag!!!')
exit()
elif inp[38] + inp[25] + inp[1] + inp[23] + inp[28] + inp[27] != 403:
print('Wrong Flag!!!')
exit()
elif inp[37] + inp[11] + inp[2] + inp[24] + inp[39] + inp[21] != 457:
print('Wrong Flag!!!')
exit()
elif inp[21] + inp[4] + inp[3] + inp[11] + inp[42] + inp[2] != 588:
print('Wrong Flag!!!')
exit()
elif inp[11] + inp[36] + inp[27] + inp[1] + inp[18] + inp[19] != 549:
print('Wrong Flag!!!')
exit()
elif inp[16] + inp[18] + inp[37] + inp[41] + inp[25] + inp[45] != 446:
print('Wrong Flag!!!')
exit()
elif inp[19] + inp[19] + inp[18] + inp[8] + inp[25] + inp[14] != 453:
print('Wrong Flag!!!')
exit()
elif inp[19] + inp[2] + inp[40] + inp[34] + inp[27] + inp[5] != 461:
print('Wrong Flag!!!')
exit()
elif inp[48] + inp[41] + inp[33] + inp[41] + inp[23] + inp[37] != 533:
print('Wrong Flag!!!')
exit()
elif inp[45] + inp[9] + inp[8] + inp[32] + inp[4] + inp[26] != 531:
print('Wrong Flag!!!')
exit()
elif inp[47] + inp[27] + inp[2] + inp[32] + inp[3] + inp[38] != 393:
print('Wrong Flag!!!')
exit()
elif inp[32] + inp[27] + inp[2] + inp[34] + inp[27] + inp[14] != 506:
print('Wrong Flag!!!')
exit()
elif inp[24] + inp[14] + inp[39] + inp[20] + inp[3] + inp[17] != 365:
print('Wrong Flag!!!')
exit()
elif inp[10] + inp[17] + inp[43] + inp[28] + inp[48] + inp[48] != 565:
print('Wrong Flag!!!')
exit()
elif inp[35] + inp[47] + inp[27] + inp[42] + inp[35] + inp[37] != 415:
print('Wrong Flag!!!')
exit()
elif inp[10] + inp[37] + inp[37] + inp[44] + inp[21] + inp[15] != 502:
print('Wrong Flag!!!')
exit()
elif inp[9] + inp[44] + inp[9] + inp[48] + inp[38] + inp[15] != 600:
print('Wrong Flag!!!')
exit()
elif inp[16] + inp[47] + inp[12] + inp[27] + inp[39] + inp[16] != 386:
print('Wrong Flag!!!')
exit()
elif inp[2] + inp[37] + inp[32] + inp[41] + inp[9] + inp[13] != 485:
print('Wrong Flag!!!')
exit()
elif inp[25] + inp[18] + inp[25] + inp[41] + inp[40] + inp[11] != 566:
print('Wrong Flag!!!')
exit()
elif inp[36] + inp[37] + inp[4] + inp[12] + inp[35] + inp[42] != 546:
print('Wrong Flag!!!')
exit()
elif inp[45] + inp[32] + inp[12] + inp[19] + inp[16] + inp[3] != 371:
print('Wrong Flag!!!')
exit()
print('Correct!!! Here is your flag: ' + flag)
```
- Rất dễ dàng chúng ta có thể lấy được các phương trình này ra, thành lập hệ phương trình là có thể giải dễ dàng, thêm hai hint nữa với format flag là `KCSC{}`, `len(flag) == 49`. Ở đây mình dùng `sage` để giải do khá thích syntax của `sage` (tương tự với python, các bạn có thể thử đọc, đặc biệt khuyến khích những ai muốn chơi crypto), còn `z3` thì cũng oke nhưng `z3` sẽ mạnh ở `BitVec` mà chúng ta sẽ sử dụng ở một bài `Medium` ngay dưới đây thôi:
```python=
x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48 = var("x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48")
inp = [x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48]
polys = []
f = inp[0] - ord("K")
polys.append(f)
f = inp[1] - ord("C")
polys.append(f)
f = inp[2] - ord("S")
polys.append(f)
f = inp[3] - ord("C")
polys.append(f)
f = inp[4] - ord("{")
polys.append(f)
f = inp[-1] - ord("}")
polys.append(f)
f = inp[30] + inp[44] + inp[16] + inp[38] + inp[47] + inp[7] - 398
polys.append(f)
f = inp[41] + inp[22] + inp[38] + inp[33] + inp[28] + inp[20] - 451
polys.append(f)
f = inp[10] + inp[3] + inp[39] + inp[14] + inp[4] + inp[47] - 440
polys.append(f)
f = inp[2] + inp[12] + inp[45] + inp[4] + inp[42] + inp[30] - 581
polys.append(f)
f = inp[36] + inp[36] + inp[26] + inp[43] + inp[21] + inp[1] - 587
polys.append(f)
f = inp[16] + inp[3] + inp[16] + inp[20] + inp[38] + inp[39] - 274
polys.append(f)
f = inp[28] + inp[39] + inp[18] + inp[38] + inp[47] + inp[8] - 372
polys.append(f)
f = inp[25] + inp[19] + inp[36] + inp[19] + inp[20] + inp[31] - 470
polys.append(f)
f = inp[44] + inp[27] + inp[5] + inp[41] + inp[16] + inp[42] - 565
polys.append(f)
f = inp[46] + inp[35] + inp[8] + inp[1] + inp[4] + inp[47] - 447
polys.append(f)
f = inp[41] + inp[20] + inp[42] + inp[40] + inp[3] + inp[43] - 503
polys.append(f)
f = inp[36] + inp[4] + inp[21] + inp[46] + inp[34] + inp[38] - 532
polys.append(f)
f = inp[43] + inp[45] + inp[3] + inp[45] + inp[3] + inp[17] - 382
polys.append(f)
f = inp[24] + inp[2] + inp[6] + inp[2] + inp[25] + inp[1] - 490
polys.append(f)
f = inp[38] + inp[41] + inp[33] + inp[34] + inp[21] + inp[42] - 569
polys.append(f)
f = inp[17] + inp[38] + inp[1] + inp[15] + inp[46] + inp[35] - 364
polys.append(f)
f = inp[40] + inp[17] + inp[34] + inp[33] + inp[39] + inp[19] - 398
polys.append(f)
f = inp[18] + inp[21] + inp[4] + inp[27] + inp[19] + inp[29] - 541
polys.append(f)
f = inp[30] + inp[34] + inp[42] + inp[26] + inp[18] + inp[47] - 588
polys.append(f)
f = inp[23] + inp[24] + inp[30] + inp[1] + inp[13] + inp[7] - 471
polys.append(f)
f = inp[17] + inp[16] + inp[32] + inp[16] + inp[15] + inp[14] - 343
polys.append(f)
f = inp[30] + inp[10] + inp[24] + inp[3] + inp[40] + inp[3] - 519
polys.append(f)
f = inp[10] + inp[34] + inp[27] + inp[38] + inp[46] + inp[40] - 480
polys.append(f)
f = inp[6] + inp[6] + inp[46] + inp[35] + inp[5] + inp[13] - 357
polys.append(f)
f = inp[18] + inp[16] + inp[5] + inp[6] + inp[12] + inp[32] - 411
polys.append(f)
f = inp[1] + inp[3] + inp[37] + inp[4] + inp[22] + inp[44] - 514
polys.append(f)
f = inp[26] + inp[11] + inp[12] + inp[47] + inp[22] + inp[2] - 541
polys.append(f)
f = inp[32] + inp[32] + inp[18] + inp[34] + inp[31] + inp[37] - 454
polys.append(f)
f = inp[38] + inp[25] + inp[1] + inp[23] + inp[28] + inp[27] - 403
polys.append(f)
f = inp[37] + inp[11] + inp[2] + inp[24] + inp[39] + inp[21] - 457
polys.append(f)
f = inp[21] + inp[4] + inp[3] + inp[11] + inp[42] + inp[2] - 588
polys.append(f)
f = inp[11] + inp[36] + inp[27] + inp[1] + inp[18] + inp[19] - 549
polys.append(f)
f = inp[16] + inp[18] + inp[37] + inp[41] + inp[25] + inp[45] - 446
polys.append(f)
f = inp[19] + inp[19] + inp[18] + inp[8] + inp[25] + inp[14] - 453
polys.append(f)
f = inp[19] + inp[2] + inp[40] + inp[34] + inp[27] + inp[5] - 461
polys.append(f)
f = inp[48] + inp[41] + inp[33] + inp[41] + inp[23] + inp[37] - 533
polys.append(f)
f = inp[45] + inp[9] + inp[8] + inp[32] + inp[4] + inp[26] - 531
polys.append(f)
f = inp[47] + inp[27] + inp[2] + inp[32] + inp[3] + inp[38] - 393
polys.append(f)
f = inp[32] + inp[27] + inp[2] + inp[34] + inp[27] + inp[14] - 506
polys.append(f)
f = inp[24] + inp[14] + inp[39] + inp[20] + inp[3] + inp[17] - 365
polys.append(f)
f = inp[10] + inp[17] + inp[43] + inp[28] + inp[48] + inp[48] - 565
polys.append(f)
f = inp[35] + inp[47] + inp[27] + inp[42] + inp[35] + inp[37] - 415
polys.append(f)
f = inp[10] + inp[37] + inp[37] + inp[44] + inp[21] + inp[15] - 502
polys.append(f)
f = inp[9] + inp[44] + inp[9] + inp[48] + inp[38] + inp[15] - 600
polys.append(f)
f = inp[16] + inp[47] + inp[12] + inp[27] + inp[39] + inp[16] - 386
polys.append(f)
f = inp[2] + inp[37] + inp[32] + inp[41] + inp[9] + inp[13] - 485
polys.append(f)
f = inp[25] + inp[18] + inp[25] + inp[41] + inp[40] + inp[11] - 566
polys.append(f)
f = inp[36] + inp[37] + inp[4] + inp[12] + inp[35] + inp[42] - 546
polys.append(f)
f = inp[45] + inp[32] + inp[12] + inp[19] + inp[16] + inp[3] - 371
polys.append(f)
sol = solve(polys, inp)[0]
s = [chr(int(str(sol[i]).split("==" )[1])) for i in range(49)]
flag = ''.join(s)
print(flag)
```
- Và chúng ta nhận được:

> ~~`KCSC{700_much_1f-3l53_f0r_fl46ch3ck3r!!!7ry_z3<3}`~~
- Note một lời khuyên cho các bạn khi gặp bài này là không nên ngồi copy paste từng phương trình ra, mà hãy sử dụng phím tắt `Ctrl + F2` của `VScode` và ngồi edit lại các dòng phương trình, như thế sẽ nhanh và đỡ đụt hơn (mình làm bài này mất 5p nhờ cách này :v)
### 4. Go (Easy - noobmannn)
- Không hiểu sao nhưng có vẻ sếp Bình khá thích chơi ngôn ngữ lạ (may mà chưa tới `Rust`). Âu kê, chúng ta có một file `exe` ở đây, mình cũng không sợ lắm nên run thôi:

- Và chương trình trả ra sai độ dài, nếu nhập tiếp thì vẫn sai thôi nên mình quăng thẳng vào ida để phân tích. Như mọi lần, mình sẽ tìm vào subview `Imports` để check xem có anti-debug không, có vẻ là không:

- Vậy thì tìm chuỗi mình vừa thấy trên console và `xref` thôi. Khi mình giải thì mình sẽ `F5` gen mã giả nhưng khi viết wu thì ưu tiên phân tích code Assembly hơn (chỉ là một cách để mình học thôi). Sau khi lướt xuống dưới một chút thì mình thấy được các hàm của Golang, nói chung là không quá quan trọng.
- Và mình tới được đây:

- Chương trình sẽ check độ dài flag với 51, nếu sai thì `call os_Exit` luôn, cũng không quan trọng, đồng thời mình sẽ đổi tên một số biến trong stack để dễ trace hơn. Sau khi `Fscanln` thì hiện tại `[rsp+228h+var_60]` đang giữ offset trỏ tới offset `input` của chúng ta, nghe giống con trỏ cấp 2 nhưng cũng chẳng cần quan tâm lắm. Mình nghĩ khi phân tích linear giống kiểu mình thì không cần quan tâm tiểu tiết quá trừ khi muốn bị tiền đình :v, những phần nào khó hiểu mình sẽ ưu tiên debug. Sau đó thực hiện `mov rcx, [rsp+228h+var_60]` để đẩy offset vào `rcx`. Một điều khá hay mà khi viết wu mình mới nhận ra là `rcx` lưu cả offset của `input` và `len(input)` ngay cạnh luôn:

- Ở đây mình chọn `input = "KCSC{0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHI}"`. `F8` xuống dưới, có thể thấy thông báo `Correct`:

- Và bước bên cạnh chính là phần flag checker. Bên dưới đó chỉ là các bước lấy kí tự của flag, đem `xor` với một kí tự của một chuỗi (`r10`), và so sánh với kí tự của mảng (`var_1F8`):

- Vậy mình cần extract lấy kí tự của chuỗi và mảng. Với mảng thì chỉ cần lấy `8 * 51 = 408` byte thôi do mỗi byte cách nhau 8 số 0:

- Tuy nhiên, với chuỗi thì cần chú ý, dịch lên graph một chút, mình thấy:

- Ở đây, chỉ số của chuỗi được lưu vào trong `r8` và `rax`. Sau đó thực hiện backup chỉ số từ `r8` vào `r9` (dùng cho mảng), còn `rax` thì được `and` với `0FFFFFFFFFFFFFFE0h`. Phân tích một chút, nếu mình thực hiện `and` toàn bộ các chỉ số từ 0 đến 51 với số này, sẽ nhận được:

và nếu thực hiện `sub r8, rax`, sẽ được:

- Vậy chúng ta kết luận, chương trình sẽ lấy các chỉ số của mảng rồi `mod 32`, vậy chuỗi chỉ gồm 32 kí tự, chính là chuỗi này `YXV0aG9ybm9vYm1hbm5uZnJvbWtjc2M=` (đem dịch ra sẽ thấy điều đặc biệt). Sau khi có được chuỗi và mảng, chúng ta thực hiện phép `xor` để ra được flag thôi, code:
```python=
from base64 import b64decode
arr = [18, 27, 5, 115, 26, 112, 81, 72, 87, 50, 8, 67, 6, 94, 5, 93, 27, 91, 5, 25, 110, 0, 124, 41, 1, 63, 64, 6, 15, 1, 35, 11, 106, 7, 97, 85, 0, 117, 93, 24, 83, 90, 102, 74, 106, 81, 2, 73, 67, 76, 72]
s = "YXV0aG9ybm9vYm1hbm5uZnJvbWtjc2M="
print(b64decode(s))
for i in range(51):
print(chr(arr[i] ^ ord(s[i %32])), end="")
```
> ~~`KCSC{7h15_15_345y60l4n6_ch4ll3n63_7ea2da17_<3<3!!!}`~~
- Note, chuỗi base64 trên chính là encode của cre bài này (author là noobmannn). chuỗi này cũng xuất hiện ở bài `Shelter` dưới đây thui. Nếu thấy giải như này lâu quá thì các bạn có thể `F5` thẳng ra cũng được nhưng mình khuyên vẫn là nên đọc mã máy để đi xa hơn trong mảng rev này :kissing_smiling_eyes:
### 5. ramsom (Easy - hansha/1conmeo)
- Bước vào chall, chúng ta có hai file, một là chương trình `Encrypt`, hai là file `important`:

- Thực ra lúc làm bài, mình cũng để ý file `important` là một file bị mã hóa, nhưng thấy các kí tự mã hóa ghê quá nên nhảy qua phân tích file `Encrypt` luôn. Tuy nhiên mình đoán cũng có khá nhiều bạn sẽ phang thẳng file `Encrypt` và từ đó file `important` được decrypted lại thành readable code luôn.
- Với `Encrypt`, khi thực thi file cần truyền vào tên file, mục đích là để mã hóa nó, các hàm trong đây cũng gợi ý cho ta biết chương trình sẽ tương tác với file:

- Và nó có cả `IsDebuggerPresent` nhưng không cần quan tâm lắm (vì mình đặt bp nhưng lệnh call không được hit). Phần tương tác với file thì chúng ta lướt qua rất nhanh thôi, chủ yếu là mở file, lấy kích thước file để xác định số kí tự cần mã hóa, và thực hiện thuật toán. Ở đây mình bắt gặp thuật toán khá giống mã hóa dòng (stream cipher) RC4 kết hợp với các phép ROR, ROL:

- Tuy nhiên, dễ dàng thấy rằng, từ dòng 34-41, chương trình thực hiện ROR-ROL thì dòng 53-60 lại thực hiện ROL-ROR với cùng số bit, mình sẽ test xem quá trình mã hóa này có đồng thời là giải mã không:

- That's it, thực ra tới đây mình đã dự đoán ngay được thuật toán mã hóa của chương trình đồng thời là giải mã luôn (và chính xác là như vậy do phép `xor` cũng là inversible). Tiếp đó chỉ là bước ghi đè bản mã lên file mà thôi.
- Khi giải nén ra hai file trên, mình nghĩ file `important` được mã hóa từ chính chương trình này và loay hoay tìm cách reverse. Nhưng sau khi phân tích thì không cần viết lại chương trình `Decrypt`, hãy để `Encrypt` làm công việc của nó:

- Giờ thì dùng `DIE` phân tích file `important` thôi:

- Chính là file `C#`, mình sẽ sử dụng `dnSpy` để phân tích chương trình (cứ để định dạng trống hoặc đổi thành `.exe` để chạy cũng được), vì là troller nên tốt nhất mở bằng tool cho lành:

- Đây là main của chương trình, một code với fake flag rất ý nghĩa cùng các dòng `WriteLine` xuất sắc. Đến được đây thì author đã gợi ý rằng chúng ta đang đi đúng hướng, giờ sẽ quay lại hàm `Encryptor` xem có gì không:

- Nhờ việc từng học Crypto dù hơi gà nên mình có thể đoán lờ mờ hàm sử dụng AES-CBC để mã hóa cho bản rõ là input vào chương trình. Với mode này, để giải mã cần bản mã (enc), `iv`, và `key`. Với bản mã, có thể dạo xuống dưới hàm `Resource1` để thấy:

đề cập tới một chuỗi static nằm trong `Resources` của chall, nó chính là chuỗi nằm trong phần này:

- Do `Encryptor` mã hóa rồi chuyển thành base64 nên khá sure đây là `enc` của chúng ta. Bên cạnh đó, có thể thấy một số tham số được sử dụng:

- Và dựa vào cách khởi tạo hàm mã hóa, mình thấy:

- Biến `bytes` có 16 bytes lấy từ chuỗi `s2`, trong khi đó, `bytes 3` có 32 bytes do chạy hàm `GetBytes(256/8) = GetBytes(32)` khiến mình nghĩ `s2` chính là `iv` và `bytes 3` chính là `key` của quá trình. Do không nắm rõ thuật toán của `PasswordDeriveBytes` (nó ở [đây](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.passwordderivebytes.-ctor?view=net-8.0#system-security-cryptography-passwordderivebytes-ctor(system-string-system-byte()-system-string-system-int32)
)) nên mình sẽ sử dụng code `C#` để lấy lại `key` và cả `iv` luôn:
```c#=
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Solve
{
class Program
{
static string strPassword = "amp4Z0wpKzJ5Cg0GDT5sJD0sMw0IDAsaGQ1Afik6NwXr6rrSEQE=";
static string s = "aGQ1Afik6NampDT5sJEQE4Z0wpsMw0IDAD06rrSswXrKzJ5Cg0G=";
static string strHashName = "SHA1";
static int iterations = 2;
static int num = 256;
static string s2 = "@bbd1ec93108b0cb";
static void Main(string[] args)
{
byte[] bytes = Encoding.ASCII.GetBytes(s2);
byte[] bytes2 = Encoding.ASCII.GetBytes(s);
byte[] bytes3 = new PasswordDeriveBytes(strPassword, bytes2, strHashName, iterations).GetBytes(num / 8);
Console.WriteLine("Key = " + BitConverter.ToString(bytes3));
Console.WriteLine("IV = " + BitConverter.ToString(bytes));
}
}
}
```
- Và nhận được:

- Vác đống này đi giải flag thôi:
```python=
from Crypto.Cipher import AES
from base64 import b64decode as d
enc = "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"
Key = "34-88-6D-5B-09-7A-94-19-78-D0-E3-8B-1B-5C-A3-29-60-74-6A-5E-5D-64-87-11-B1-2C-67-AA-5B-3A-8E-BF"
IV = "40-62-62-64-31-65-63-39-33-31-30-38-62-30-63-62"
enc = d(enc)
Key = bytes.fromhex(Key.replace("-", ""))
IV = bytes.fromhex(IV.replace("-", ""))
cipher = AES.new(key=Key, mode=AES.MODE_CBC, iv=IV)
flag = cipher.decrypt(enc).strip()
print(flag)
```
nhận được:

- Tuy nhiên base64 này bị lỗi padding nên mình phải thêm `=` vào mới giải ra đúng flag:

> ~~`KCSC{anh khong muon boc dau vi anh khong co em dang
sau <3}`~~
- Note, chall đúng chất troller, tuy hay, nhưng gây ức chế cho người làm nên các bạn bình tĩnh nha, vào CLB cũng bị anh Khiêm troll thôi nên làm quen dần :face_with_hand_over_mouth:.
### 6. Từng quen (Easy - hansha/1conmeo)
- Với bài này, mình được cung cấp 1 file `sample.exe` là một chương trình mã hóa file, tên file là `important_note.txt`, và được mã hóa cùng kiểu với bài `Time travel` trên, là mã hóa với `seed` và `rand()`. Bài ở trên thì `seed` chạy rất gần với ngày quy ước, còn ở đây thì là ngày tạo file. Với file `important_note.txt` được mã hóa trong tay, chúng ta có thể thực hiện kỹ thuật tương tự với bài du hành thời gian trên và lụm được flag:
```c=
#include <bits/stdc++.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
int main(){
int flag[100] = {244, 182, 116, 71, 56, 26, 174, 101, 212, 130, 165, 187, 234, 159, 208, 135, 159, 151, 72, 109, 99, 6, 169, 101, 154, 195, 174, 190, 234, 138, 214, 154, 222, 213, 85, 107, 42, 84, 178, 101, 215, 130, 165, 187, 234, 146, 223, 130, 159, 153, 72, 109, 99, 21, 168, 100, 154, 193, 168, 163, 171, 222, 202, 154, 209, 146, 7, 96, 34, 25, 230, 98, 213, 203, 224, 183, 164, 150, 158, 150, 218, 128, 7, 105, 44, 0, 230, 109, 211, 130, 180, 190, 175, 222, 208, 142, 198, 212, 6, 121};
int check[5];
int found = 0;
long long start_time = 1722513600;
long long end_time = 1726401600;
time_t epoch1 = start_time;
time_t epoch2 = end_time;
printf("%s", asctime(gmtime(&epoch1)));
printf("%s", asctime(gmtime(&epoch2)));
for(long long i=start_time; i<=end_time; i++){
srand(i);
for(int j=0; j<5; j++) check[j] = flag[j] ^ (rand() & 0xFF);
if(check[0] == 75 && check[1] == 67 && check[2] == 83 && check[3] == 67 && check[4] == 123){
found = 1;
printf("Found at %s\n", asctime(gmtime(&i)));
}
}
if(!found) printf("Not found");
return 0;
}
```
- Nếu chỉ giải đơn thuần như bài trên thì được:

- Oh yeah, ai cũng biết flag là gì, nhưng chúng ta đang học RE chứ không phải học mò flag. Mở file `sample.exe` lên!

- Tại đây, thay vì `xor rand()` liên tọi như bài trước thì author cho `rand()` vào thành một mảng có `0x10 = 16` phần tử, và lần lượt thay thế các giá trị ở vị trí `0xC`, `0xD`, `0xE`, `0xF` bằng các `dword` như trong hình. Tuy nhiên nếu các bạn phân tích động (như mình lúc đầu làm bài) thì sẽ bị vả ngay cái anti-debug vào mồm. Lí do là đây:

- Như bài `TTV2023` đã nói, `TLS` chạy trước hàm main và bị ẩn đi trong mắt debugger, và lần này là một phương pháp khác: `NtQueryInformationProcess`. Nói nôm na, giá trị mặc định của `ProcessInformationClass` tại cổng debug là 7 (theo [nguồn](https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess)), nếu đang ở trong debugger, giá trị này sẽ lập tức trở thành -1, và chúng ta sẽ bị dắt mũi vì 4 value cuối trong mảng `rand()` sẽ bị thay đổi. Nếu phân tích tĩnh thì có thể vượt qua bẫy này dễ dàng, nhưng mình vẫn muốn phân tích để có cái nhìn tổng quan hơn. Các kĩ thuật anti-debug khác có thể tham khảo thêm ở [đây](https://anti-debug.checkpoint.com/techniques/debug-flags.html#using-win32-api-ntqueryinformationprocess-processdebugport). Chứng minh khi debug thì `ProcessInformationClass = -1`:

- Vậy là đã rõ, chỉ cần sửa lại code một chút, ta có được flag:

> ~~`KCSC{nhin em anh boi roi anh thua roi tim em lam loi anh chua tung dam noi anh yeu mot ai the nay!!}`~~
### 7. s1mple flag checker but no SEE (Medium - 13r_ə_Rɪst)
- Đúng như tên gọi của bài, chúng ta có một flag checker không phải C mà là Java. Với mình thì bài này không khó, đặt ở mức Medium cũng khá hợp lí. Code bài chỉ là một chút obsfucate và z3, tuy nhiên nếu chú ý kĩ và cẩn thận thì bài này trong tầm tay. Một lưu ý nữa cần chú ý phân biệt biến cục bộ và biến tĩnh.
- Mình đã deobsfucate bài này thành một flag checker đơn giản hơn:
```python=
import java.util.*;
public class Flag_Checker {
static int[] pos = { 10, 15, 19, 28, 30, 44, 49 };
static int[] f1nal = { 18, 20, 22, 23, 25, 26, 27, 31, 33, 34, 36, 37, 39, 40 };
public static boolean checkKernel(boolean ret, String s) {
if ((!s.substring(0, 5).equals("KCSC{")) || !(s.charAt(s.length() - 1) == '}'))
ret = false;
return ret;
}
public static boolean checkUnderscore(boolean ret, String s, int[] arr) {
for (int i = 0; i < arr.length; ++i) {
if (s.charAt(arr[i]) != 95)
return false;
}
return ret;
}
public static boolean checkSth(boolean ret, String s, boolean[] arr) {
int[] num = new int[10];
for (int i = 0; i < s.length(); ++i) {
if (Character.isDigit(s.charAt(i)) != arr[i])
ret = false;
}
for (int i = 0; i < pos.length; ++i) {
num[i] = s.charAt(pos[i]) - 0x30;
}
if ((num[1] != num[6])
// || (num[0] != num[1] - num[6])
// || (num[3] + num[4] != num[1])
|| (num[6] * num[5] != 20))
// || (num[0] != num[2])
// || (Math.pow(num[3], num[5]) != 256)
// || ((num[1] ^ num[4]) != 4)
// || (num[1] != 5)) {
ret = false;
return ret;
}
public static boolean checkChar2Pointers(boolean ret, String s, char[] arr) {
int l = 5, r = 55, cnt = 0;
while (cnt < arr.length) {
while (!Character.isLetter(s.charAt(l)))
l++;
while (!Character.isLetter(s.charAt(r)))
r--;
if (s.charAt(r) != arr[cnt] || s.charAt(l) != arr[cnt + 1]) {
ret = false;
}
cnt += 2;
l++;
r--;
}
return ret;
}
public static boolean checkFinal(boolean ret, String s, String tmp) {
for (int i = 0; i < f1nal.length; ++i)
tmp += s.charAt(f1nal[i]);
if (((tmp.charAt(2) ^ tmp.charAt(0)) != 32)
|| (tmp.charAt(0) + tmp.charAt(5) + tmp.charAt(6) != 294)
|| (tmp.charAt(1) * tmp.charAt(3) != 8160)
|| ((tmp.charAt(3) ^ tmp.charAt(4)) != 44)
|| ((tmp.charAt(2) ^ tmp.charAt(3)) != 9)
|| (tmp.charAt(0) * tmp.charAt(3) != 8058)
|| (tmp.charAt(3) - tmp.charAt(4) != 28)
|| ((tmp.charAt(2) ^ tmp.charAt(7)) != 28)
|| (tmp.charAt(12) - tmp.charAt(13) + tmp.charAt(9) - tmp.charAt(8) != 38)
|| (tmp.charAt(3) - tmp.charAt(4) != 28)
|| ((tmp.charAt(2) ^ tmp.charAt(11)) != 0)
|| (tmp.charAt(4) - tmp.charAt(6) != -44)
|| ((tmp.charAt(6) ^ tmp.charAt(8)) != 19)
|| (tmp.charAt(9) - tmp.charAt(5) != 25)
|| (tmp.charAt(0) + tmp.charAt(5) + tmp.charAt(7) != 291)
|| ((tmp.charAt(10) ^ tmp.charAt(5)) != 21)
|| (tmp.charAt(1) != tmp.charAt(13))
|| (tmp.charAt(11) != 111)
|| (s.charAt(s.length() - 2) != 63)) {
ret = false;
}
return ret;
}
public static void main(String[] args) {
boolean[] isDigit = { false, false, false, false, false, false, false, false, false, false, true, false,
false, false, false, true, false, false, false, true, false, false, false, false, false, false, false,
false, true, false, true, false, false, false, false, false, false, false, false, false, false, false,
false, false, true, false, false, false, false, true, false, false, false, false, false, false, false };
int[] pos = { 17, 21, 24, 29, 32, 35, 38, 47, 52 };
char[] let = { 't', 'P', 'i', 'o', 't', 'L', 'n', 'y', 'i', 'm', 'h', 'r', 'c', 'p', 'o', 'h', 'r', 'i',
'P', 'm' };
for (int i = 0; i < f1nal.length; ++i)
System.out.println(pos[i]);
Scanner inp = new Scanner(System.in);
System.out.print("Enter flag: ");
String input = inp.next();
boolean ret = true;
boolean kernelCheck = checkKernel(ret, input);
boolean Sthcheck = checkSth(kernelCheck, input, isDigit);
boolean underscoreCheck = checkUnderscore(Sthcheck, input, pos);
boolean char2PointersCheck = checkChar2Pointers(underscoreCheck, input, let);
boolean finalCheck = checkFinal(char2PointersCheck, input, "");
if (finalCheck)
System.out.println("Flag is correct");
else
System.out.println("Try another one");
}
}
```
- Bên cạnh đó, mình sử dụng z3 ở hai hàm `Sthcheck` và `checkFinal`, các bạn có thể tham khảo code của mình ở đây:
```python=
from z3 import *
x0,x1,x2,x3,x4,x5,x6,x7 = BitVecs("x0 x1 x2 x3 x4 x5 x6 x7", 8)
num = [x0,x1,x2,x3,x4,x5,x6,x7]
s = Solver()
s.add(num[1] == num[6])
s.add(num[0] == num[1] - num[6])
s.add(num[3] + num[4] == num[1])
s.add(num[6] * num[5] == 20)
s.add(num[0] == num[2])
# s.add(pow(num[3], num[5]) == 256)
s.add(num[1] ^ num[4] == 4)
s.add(num[1] == 5)
if s.check() == sat:
m = s.model()
for i in range(len(m)):
c = (m[num[i]].as_long() + 0x30)
num[i] = chr(c)
print(num) # Sth check
final = [18, 20, 22, 23, 25, 26, 27, 31, 33, 34, 36, 37, 39, 40]
x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13 = BitVecs("x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13", 8)
tmp = [x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13]
polys = []
s = Solver()
s.add(tmp[2] ^ tmp[0] == 32)
s.add(tmp[0] + tmp[5] + tmp[6] == 294)
s.add(tmp[1] * tmp[3] == 8160)
s.add(tmp[3] ^ tmp[4] == 44)
s.add(tmp[2] ^ tmp[3] == 9)
s.add(tmp[0] * tmp[3] == 8058)
s.add(tmp[3] - tmp[4] == 28)
s.add(tmp[2] ^ tmp[7] == 28)
s.add(tmp[12] - tmp[13] + tmp[9] - tmp[8] == 38)
s.add(tmp[3] - tmp[4] == 28)
s.add(tmp[2] ^ tmp[11] == 0)
s.add(tmp[4] - tmp[6] == -44)
s.add(tmp[6] ^ tmp[8] == 19)
s.add(tmp[9] - tmp[5] == 25)
s.add(tmp[0] + tmp[5] + tmp[7] == 291)
s.add(tmp[10] ^ tmp[5] == 21)
s.add(tmp[1] == tmp[13])
s.add(tmp[11] == 111)
if s.check() == sat:
m = s.model()
for i in range(14):
tmp[i] = m[tmp[i]].as_long()
print(tmp) #Final check
```
- Sử dụng z3 rất đơn giản mà thôi, các bạn có thể tìm đọc docs ở [đây](https://microsoft.github.io/z3guide/programming/Z3%20Python%20-%20Readonly/Introduction/), mình sử dụng `BitVecs` phục vụ cho phép `xor` có trong một số phương trình, đây là chức năng mạnh của z3 vượt trội hơn so với sage nên hãy linh hoạt sử dụng cả hai công cụ nhé. Code giải:
```python=
from string import *
LEN = 57
flag = "KCSC{}"
flag = "KCSC{" + "?" * (LEN - len(flag)) + "}"
print(f"Initial: {flag}")
pos = [17, 21, 24, 29, 32, 35, 38, 47, 52]
def underscoreCheck(flag, pos):
flag = list(flag)
for p in pos:
flag[p] = "_"
return ''.join(flag)
flag = underscoreCheck(flag, pos)
print(f"After underscoreCheck: {flag}")
tmp = [79, 80, 111, 102, 74, 97, 118, 115, 101, 122, 116, 111, 97, 80]
final = [18, 20, 22, 23, 25, 26, 27, 31, 33, 34, 36, 37, 39, 40]
def finalCheck(flag, tmp, final):
flag = list(flag)
for i in range(len(final)):
flag[final[i]] = chr(tmp[i])
return ''.join(flag)
flag = finalCheck(flag, tmp, final)
print(f"After finalCheck: {flag}")
isDigit = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, True, False, False, False, True, False, False, False, False, False, False, False, False, True, False, True, False, False, False, False, False, False, False, False, False, False, False,False, False, True, False, False, False, False, True, False, False, False, False, False, False, False]
num = ['0', '5', '0', '4', '1', '4', '5']
def Sthcheck(flag, num, isDigit):
flag = list(flag)
cnt = 0
for i in range(len(isDigit)):
if isDigit[i]:
flag[i] = num[cnt]
cnt += 1
return ''.join(flag)
flag = Sthcheck(flag, num, isDigit)
print(f"After Sthcheck: {flag}")
let = ['t', 'P', 'i', 'o', 't', 'L', 'n', 'y', 'i', 'm', 'h', 'r', 'c', 'p', 'o', 'h', 'r', 'i', 'P', 'm']
def char2PointersCheck(flag, let):
flagl = list(flag)
flagr = list(flag)
flag = list(flag)
ll = lr = 5
rl = rr = 54
cntl = cntr = 0
while cntl < len(let):
while flagl[ll] != "?":
ll += 1
while flagl[rl] != "?":
rl -= 1
flagl[ll] = let[cntl+1]
cntl += 2
ll += 1
rl -= 1
while cntr < len(let):
while flagr[lr] != "?":
lr += 1
while flagr[rr] != "?":
rr -= 1
flagr[rr] = let[cntr]
cntr += 2
lr += 1
rr -= 1
for i in range(5, len(flag)-2, 1):
flag[i] = flagl[i] if flagl[i] != "?" else flagr[i]
return(''.join(flag))
flag = char2PointersCheck(flag, let)
print(f"After char2PointersCheck: {flag}")
```
> ~~`KCSC{PoLym0rphi5m_O0P_of_Jav4_1s_ez_to_aPPro4ch_i5nt_it?}`~~
- Note, bài này mình hơi lười viết lời giải vì nó clear quá :v
### 8. Shelter (Medium - noobmannn)
- Với desc liên quan đến một số Crypto, mình nghĩ khi phân tích file hãy luôn bear in mind điều này. Chúng ta được cung cấp một chương trình flag checker, chưa cần run, mình sẽ đem vào ida để phân tích:

- Hàm `main` rất ngắn gọn thôi để call các hàm con cho dễ, sau một hồi phân tích thì mình đổi được tên hàm như sau:

- Đây là một bài rất hay nên mình sẽ cố gắng phân tích nó thật kĩ:
#### 8.1. paddingStage
- Tại đây, chương trình tính toán để lại được độ dài của `input` và số lượng kí tự cần pad thêm để đủ một bội của 16 (đây là padding thường thấy trong mã hóa khối), phần này thì nhìn mã giả cũng được thôi nhưng mình nghĩ nên nhìn mã máy để hiểu rõ. Ở đây, độ dài tối đa `input` là `0x50 = 80`, cốt là vì trong một số thuật toán padding, kể cả khi đã thỏa là một bội của 16 thì vẫn pad thêm 16 kí tự nữa, thành ra mảng `Src` của chúng ta sẽ lưu thừa kí tự làm tràn sang các vùng nhớ khác:

- Các kí tự pad vào sẽ chỉ là NULL mà thôi, nên khá khó để biết `input` đã được pad thêm hay chưa nếu đặt bp ngay sau hàm `paddingStage`. Các bạn có thể `F8` liên tục để xem quá trình pad nhé.
#### 8.2. sha256_EncryptStage/md5_EncryptStage
- Hai bước này mình gộp chung vì tư duy khá giống nhau, cũng là call các API để mã hóa thui. Dưới đây là một list những API mã hóa:

- Và các chú thích của hàm đã được viết rất rõ ở [đây](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/), các bạn hãy tìm đọc nha. Còn trong phần này thì cần chú ý tới một số hàm sau:
- `CryptCreateHash`: Tạo hàm băm với thuật toán là tham số thứ hai - `0x800Cu(sha256)`, `0x8003u(md5)`
- `CryptHashData`: Thực hiện băm chuỗi `pbData`
- `CryptGetHashParam`: Ghi đè `n` kí tự (`n` = tham số thứ 4) của chuỗi băm trong `CryptHashData` vào địa chỉ mong muốn (tham số thứ 3)
- Và ở cả hai bước này, `pbData` đều là chuỗi `https://www.youtube.com/watch?v=fzQ6gRAEoy0` (không phải rickroll đâu :penguin:). Đây là phần khiến mình đau đầu nhất, mong các bạn cũng vậy khi làm bài này; nhưng tóm lại, sau hai bước này, chúng ta thu được chuỗi băm md5 `m` và sha256 `s` của `pbData` nói trên. Rất nhanh thui sẽ có được:
```python=
s = "bb8c8c0951e81c3c568c7281a396463b0f0e0587bea99a267af159832e717f06"
m = "3ec2cd75414560e8d275a6bf6fc3751d"
```
- Các bạn có thể cắm bp check lại chuỗi trong ida nhé.
#### 8.3. final_EncryptStage
- Bước này cũng là một bước rất khó hiểu, mình mất khá nhiều thời gian để đọc docs về nó. Ở đây cần chú ý tới các hàm:
##### `CryptImportKey`
- Có thể các bạn sẽ thắc mắc khá nhiều về `pbData` của hàm này, nhưng đơn giản phần khai báo `pbData` rồi `qmemcpy` kia chỉ để phục vụ cái này:

- 12 bytes đầu kia chỉ đơn giản là header thui, điều quan trọng là chuỗi `s` sau đó đã được gắn ngay sau `pbData`, đóng vai trò là một `encrypted key - session key` trong bài. Tuy nhiên, dựa vào header có thể thấy nó tuân theo quy tắc sau:

- Ở đây, quan trọng nhất là tham số `6610h` được truyền vào `ALG_ID`, cho chúng ta biết `encrypted key` sẽ được sử dụng cho thuật toán `AES256` trong phần kế tiếp:

##### `CryptSetKeyParam`
- Thuật toán `AES256` cần gì nào, một mode, một bản rõ chính là `input` của chúng ta, một khóa chính là `encrypted key` hay `s` ở trên, và còn một `iv` nữa. Ở đây mình quan tâm tới các tham số được truyền vào hàm này:
```
CryptSetKeyParam(phKey, 4u, v6, 0) (1)
CryptSetKeyParam(phKey, 1u, &m, 0) (2)
```
- Các bạn có thể đọc (kĩ) ở [đây](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptsetkeyparam) do tùy từng tham số mà hàm sẽ hoạt động theo một phương thức khác nhau:

- Với hàm `(1)`, `pbData` được truyền vào là một `DWORD [1, 0, 0, 0]`, tức khai báo mode của `AES256` là `CBC`:

- Với hàm `(2)`, `pbData = m` lại là một mảng `BYTE` với 16 kí tự hex tương đương với 8 bytes, đó chính là `iv` của chúng ta:

- Nghe khá đơn giản và nhanh chóng nhưng mình mất cả tiếng để hiểu rằng `input` của mình sẽ được chạy qua thuật toán `AES256 - CBC`, với `key = s` và `iv = m`
##### `CryptEncrypt`:
- Hàm giúp chúng ta mã hóa chuỗi `input` với các `key`, `iv`, `MODE CBC` và ghi vào địa chỉ này:

#### 8.4. checkStage
- Chắc chắn rồi, tại đây sẽ thực hiện bước kiểm tra `input` với `flag`, chúng ta đã có thuật toán ở trên, chỉ cần vào phần này và lấy bản mã của `flag` ra là có thể hoàn thành bài rồi:

`Shift + E` để lấy data nhé. Cuối cùng là code giải bài của mình:
```python=
from hashlib import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
pbData = "https://www.youtube.com/watch?v=fzQ6gRAEoy0"
s = sha256()
s.update(pbData.encode())
key = s.hexdigest()
m = md5()
m.update(pbData.encode())
iv = m.hexdigest()
cipher = AES.new(key=bytes.fromhex(key), mode=AES.MODE_CBC, iv=bytes.fromhex(iv))
enc = ["5D", "DE", "8C", "AC", "AE", "E2", "2D", "9F", "F2", "49",
"3F", "18", "35", "09", "3C", "9E", "EF", "C5", "D1", "14",
"A5", "78", "02", "97", "18", "5A", "E8", "A0", "8E", "4C",
"DD", "19", "74", "5C", "E4", "9B", "29", "95", "B8", "D7",
"B9", "7D", "D0", "56", "BD", "94", "99", "72", "FF", "58",
"B9", "1E", "57", "E9", "DA", "27", "D5", "A9", "4D", "F5",
"B6", "3B", "07", "46", "C8", "DB", "37", "6E", "77", "95",
"97", "FA", "7F", "5D", "4D", "54", "86", "DA", "E3", "17"]
enc = ''.join([x for x in enc])
enc = bytes.fromhex(enc)
print(len(enc))
flag = cipher.decrypt(enc).decode()
print(flag)
```
> ~~`KCSC{md5_4nd_5h4256_4nd_435_w17h_w1n4p1_YXV0aG9ybm9vYm1hbm5uZnJvbWtjc2M=}
`~~
- Note, chuỗi base64 ở cuối flag chính là chữ kí của tác giả mà mình đã đề cập tới ở bài trên.
### 9. Just not a s1mple flag checker (Intermediate - 13r_ə_Rɪst)
- Sau khi giải nén, chúng ta nhận được một file chương trình flag checker. Ném vào ida để phân tích, mình biết được luồng của chương trình trước khi yêu cầu nhập `input` thì đã thực hiện loop này `0x1A7 = 423` lần:

- Tại đây có hai hàm được call khá sú, một hàm thì gọi đến `malloc`, hàm kia thì thực hiện việc gán khá khó hiểu như lắp đầu ô nhớ này vào đuôi của một ô nhớ khác:

- Tạm bỏ qua phần này, nếu bật mã giả và chạy xuống dưới một chút, chúng ta sẽ thấy một số hint sau:


- Với desc của bài là `Cảnh này thật quen thuộc, ta đã từng thấy nó ở đâu rồi. 1,2,3,4,5,6,7 anh có đánh rơi nhịp nào không~~`, chúng ta sẽ tua nhanh về môn lập trình căn bản một chút, phần kiến thức nào sẽ liên quan đến việc gán khó hiểu, việc giải phóng và chia dư kia nhỉ. Chắc chắn chính là phần kiến thức về `Danh sách liên kết`, cụ thể là xây dựng `Stack` rất quen thuộc.
- Bước gán khó hiểu ở trên thực chất là `push` các phần tử vào đầu dãy, qua rất nhiều lần debug và bấm `O` tạo offset liên tục thì mình kết luận được điều này. Bước giải phóng kia chính là `pop` các phần tử ra, và bước tính toán chính là phục vụ cho bài toán `Chuyển đổi cơ số sử dụng Stack`.
#### 9.1. Pseudocode
- Sau kết luận này, chúng ta sẽ tạo struct để sửa mã giả cho đẹp đẹp một chút:

kết hợp với sửa tên hàm và ép kiểu, ta được một code siêu đẹp:
```c=
int __cdecl main(int argc, const char **argv, const char **envp)
{
Stack *Node; // rax
Stack *v4; // rax
Stack *v5; // rax
Stack *v6; // rax
char v7; // al
int k; // [rsp+20h] [rbp-108h]
int m; // [rsp+24h] [rbp-104h]
int i; // [rsp+28h] [rbp-100h]
int j; // [rsp+2Ch] [rbp-FCh]
int n; // [rsp+34h] [rbp-F4h]
int v14; // [rsp+38h] [rbp-F0h]
__int64 input_len; // [rsp+40h] [rbp-E8h]
int v16; // [rsp+4Ch] [rbp-DCh]
Stack *v17; // [rsp+70h] [rbp-B8h]
Stack *v18; // [rsp+78h] [rbp-B0h] BYREF
Stack *v19; // [rsp+80h] [rbp-A8h] BYREF
Stack *v20; // [rsp+88h] [rbp-A0h] BYREF
char v21[16]; // [rsp+90h] [rbp-98h] BYREF
char input[112]; // [rsp+A0h] [rbp-88h] BYREF
v19 = 0LL;
v18 = 0LL;
v20 = 0LL;
for ( i = 423; i >= 0; --i )
{
Node = createNode(fake_flag[4 * i]);
push(&v20, Node);
}
printf("Show your skill: ");
scanf("%s", input);
input_len = -1LL;
do
++input_len;
while ( input[input_len] );
if ( input_len == 53 )
{
for ( j = 0; j < 53; ++j )
{
v4 = createNode(input[j]);
push(&v19, v4);
}
convertBaseAll(&v19, &v18, 0xAu, 2);
for ( k = 423; k >= 0; --k )
{
*&fake_flag3[4 * k] ^= data(v18);
freeNode(&v18);
}
for ( m = 0; m < 424; m += 2 )
{
v5 = createNode(fake_flag3[4 * m + 4]);
push(&v18, v5);
v6 = createNode(fake_flag3[4 * m]);
push(&v18, v6);
}
convertBaseAll(&v18, &v19, 2u, 8);
v14 = 0;
while ( v19 )
{
for ( n = 0; n < 8; ++n )
{
v21[n] = data(v19);
freeNode(&v19);
}
v7 = convert_Supporter2(v21);
v17 = createNode(LOBYTE(fake_flag2[v14]) ^ v7);
push(&v18, v17);
++v14;
}
while ( v18 )
{
v16 = data(v18);
if ( v16 != data(v20) )
{
printf("Nope");
return 0;
}
freeNode(&v18);
freeNode(&v20);
}
printf("Correct\n");
}
else
{
printf("Wrong input length\n");
}
return 0;
}
```
#### 9.2. Python code
- Các bạn có thể đối chiếu tên hàm và biến để đổi rồi tiện theo dõi nhé. Do code C khá tù nên từ mã giả, mình đã làm lại một code python như dưới đây:
```python=
def chunk_of_4(lst):
return [lst[i] for i in range(0, len(lst), 4)]
def insertNode(head, data):
return [data] + head
def convertBaseElement(dest, data, to_base):
i = 0
while data != 0:
dest = insertNode(dest, data % to_base)
data //= to_base
i += 1
while i < 8:
dest = insertNode(dest, 0)
i += 1
return dest
def convertBaseAll(src, dest, from_base, to_base):
if from_base == 10:
while src != []:
data = src.pop(0)
dest = convertBaseElement(dest, data, to_base)
else:
while src != []:
data_arr = []
for i in range(0, 8, 1):
data_arr.append(src.pop(0))
data = 0
for i in range(0, 8, 1):
data += data_arr[i] * (from_base ** (8 - i - 1))
dest = convertBaseElement(dest, data, to_base)
return dest
# ------------------------------------------------------------
# ENCRYPT STAGE
# ------------------------------------------------------------
def encrypt(_input, fake_flag1, fake_flag2, fake_flag3):
input_linked_list = []
check_linked_list = []
fake_linked_list = []
fake_flag = fake_flag1 + fake_flag2 + chunk_of_4(fake_flag3)
for i in range(423, -1, -1):
data = fake_flag[i]
fake_linked_list = insertNode(fake_linked_list, data)
for j in range(0, 53, 1):
data = ord(_input[j])
input_linked_list = insertNode(input_linked_list, data)
check_linked_list = convertBaseAll(input_linked_list, check_linked_list, 10, 2)
fake_flag3 = chunk_of_4(fake_flag3)
for k in range(423, -1, -1):
if check_linked_list != []:
d = check_linked_list.pop(0)
fake_flag3[k] ^= d
else:
fake_flag3[k] ^= -1
for m in range(0, 424, 2):
Node1 = fake_flag3[m+1]
check_linked_list = insertNode(check_linked_list, Node1)
Node2 = fake_flag3[m]
check_linked_list = insertNode(check_linked_list, Node2)
input_linked_list = convertBaseAll(check_linked_list, input_linked_list, 2, 8)
index = 0
while input_linked_list != []:
data_arr = []
for _ in range(0, 8, 1):
data_arr.append(input_linked_list.pop(0))
data = 0
for i in range(0, 8, 1):
data += data_arr[i] * (10 ** (8 - i - 1))
Node = (fake_flag2[index] ^ data) & 0xFF
check_linked_list = insertNode(check_linked_list, Node)
index += 1
return check_linked_list, fake_linked_list
```
với các hằng số mình `Shift + E` từ section data:
```python=
fake_flag1 = [
0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
0x24, 0x24, 0x7d, 0x67, 0x61, 0x6c, 0x66, 0x20, 0x65, 0x6b,
0x61, 0x66, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x6e, 0x20, 0x74,
0x73, 0x75, 0x6a, 0x20, 0x65, 0x72, 0x65, 0x68, 0x20, 0x67,
0x6e, 0x69, 0x68, 0x74, 0x20, 0x74, 0x6f, 0x6e, 0x7b, 0x43,
0x53, 0x43, 0x4b
]
fake_flag2 = [
0x0c2, 0x03d, 0x029, 0x0cf, 0x134, 0x0de, 0x138, 0x110, 0x0cc, 0x075,
0x13d, 0x154, 0x0f2, 0x047, 0x11b, 0x0b5, 0x087, 0x118, 0x0b6, 0x0a7,
0x104, 0x001, 0x104, 0x134, 0x004, 0x128, 0x159, 0x0f0, 0x018, 0x0f7,
0x019, 0x043, 0x10d, 0x000, 0x0e1, 0x08c, 0x0ad, 0x162, 0x153, 0x0eb,
0x0e5, 0x0da, 0x0a0, 0x0d8, 0x04c, 0x068, 0x05c, 0x0a0, 0x034, 0x0c8,
0x03e, 0x066, 0x150
]
fake_flag3 = [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
```
- Do bài sử dụng các phép `xor` và chuyển đổi cơ số khá đơn giản, các mảng đều clear và không có anti-debug lừa, chúng ta có thể dễ dàng viết script giải mã chương trình. Lí tưởng nhất thì bài chỉ có duy nhất một `flag (len = 53)`, tuy nhiên trong bài này thì không như vậy. Vấn đề nằm ở đây:

Do kiểu dữ liệu bị ép về `LOBYTE` tương đương với việc `mod 256`, thuật toán đã vô tình làm cho chall có multiple solution. Mặc dù có thể giải được flag chính xác như trong [wu của anh Tùng Dvan](https://github.com/DecemberRecruitment/SSB3YW50IHRvIGJlY29tZSBhIG1lbWJlciBvZiB0aGUgS0NTQyBjbHViLg-/tree/master/KCSC_RECRUITMENT_2024#7_just_not_a_simle_flag_checker-hard), mình vẫn brute force được và tìm ra khá nhiều solution của bài.
#### 9.3. Solution
- Dưới đây là code vũ phu của mình:
```python=
flags = [""]
while len(flags[0]) < 53:
new_flags = []
for flag in flags:
for c in range(0x20, 0x7F):
_input = flag + chr(c) * (53 - len(flag))
encrypted, fake = encrypt(_input, fake_flag1, fake_flag2, fake_flag3)
if encrypted[:len(flag)+1] == fake[:len(flag)+1]:
new_flag = flag + chr(c)
new_flags.append(new_flag)
print(f"[*] Found: {new_flag}")
flags = new_flags
```
- Nếu run code này cùng với script python ở trên, chúng ta sẽ ra được:

- Tuy nhiên, `flag` là duy nhất và có ý nghĩa nhất.
> ~~`KCSC{3V3rY_r3v3R53_En91n33r_kN0w_H0W_TH3_5t4ck_w0Rkk} `~~
### 10. Lies (Hard - noobmannn)
- Having struggled with it cuz it's fukin hard, but I've done it :smile_cat:. Một bài khép lại mùa TTV 2024 của Reverse khá hay, mình thực sự rất thích bài này, phần là vì các kĩ thuật rev cần sử dụng khá là "nhập môn" (không quá khó để thực hiện), đồng thời cách mã hóa rất thân thiện và truyền cảm hứng cao; xứng đáng là một `Lies` đáng được ghi danh, mình rất mong sẽ có `Lies Revenge` trong thời gian tới. Rất xin được cảm ơn `noobmannn with his borderland` :fire:.
- Sau khi run thử file và phân tích một chút, như mọi bài Re mình từng làm, bước đầu tiên sẽ là xác định anti-debug ở đâu. Bật `Imports` lên, mình thấy:

- Rồi, anti-debug bằng `ProcessInformation`, bên cạnh đó dễ thấy cả `VirutalProtect` (gợi cho mình suy nghĩ về function hooking), và cả `CreateToolhelp32Snapshot` nữa - đây là một kiểu anti-debug mới với mình, các bạn có thể tìm đọc tại [đây](https://anti-debug.checkpoint.com/techniques/process-memory.html#toolhelp32readprocessmemory).
- Với hàm `NtQueryInformationProcess`, mình đã giải thích ở bài trên, nếu chương trình được chạy trong trình debug, thì `ProcessInformation` sẽ là `-1` hay `0FFFFFFFFh`. Mình sẽ thẳng tay patch qua thằng này (đổi `cmp 0` thành `cmp 0FFFFFFFFh` thôi) để đỡ vướng bận lúc sau:

- IDA báo nháy xanh vào luồng đúng là oke:

- Một vấn đề đã được giải quyết xong, kế đó là `Tool32`, mình sẽ tạm bỏ qua nó để phân tích chương trình trước. Đây là một phần hàm `main`:

- Chỉ là bước in welcome và nhập `input` thôi, lướt xuống dưới sẽ thấy:

- Là một bước check length của `input` có phải 32 không và call 3 hàm khá lạ trước khi chạy đến checker:

- Vậy 3 hàm khá sú ở trên sẽ đóng vai trò gì đó, khả năng cao sẽ là các hàm mã hóa. Sau khi phân tích qua các hàm, mình đổi tên được:

#### 10.1. enc1_xor
- Hàm khá dài, mình cũng không muốn đọc mã máy lắm :v nên sẽ chơi mã giả, sau khi ép kiểu và xóa value returned, mình có:

- Dễ dàng mô phỏng lại bằng code python:
```python=
def enc1(_input):
for i in range(len(_input)):
_input[i] ^= 0xAB
for i in range(len(_input)):
_input[i] ^= i - 85
for i in range(0, len(_input), 4):
_input[i] ^= 0xC0
_input[i+1] ^= 0xFE
_input[i+2] ^= 0xBE
_input[i+3] ^= 0xEF
for i in range(0, len(_input), 4):
_input[i] ^= 0xDE
_input[i+1] ^= 0xAD
_input[i+2] ^= 0xBA
_input[i+3] ^= 0xBE
for i in range(len(_input)):
_input[i] ^= 0xCD
for i in range(len(_input)):
_input[i] ^= i - 51
for i in range(0, len(_input), 4):
_input[i] ^= 0xC0
_input[i+1] ^= 0xFE
_input[i+2] ^= 0xBA
_input[i+3] ^= 0xBE
for i in range(0, len(_input), 4):
_input[i] ^= 0xDE
_input[i+1] ^= 0xAD
_input[i+2] ^= 0xBE
_input[i+3] ^= 0xEF
for i in range(len(_input)):
_input[i] ^= 0xEF
for i in range(len(_input)):
_input[i] ^= i - 17
_input = [chr(x % 256) for x in _input]
return _input
```
- Lấy chuỗi `_input = "KCSC{0123456789abcdefghijklmnop}"`, có thể dễ dàng kiểm tra độ chính xác của code python:

với chương trình:

#### 10.2. enc2_rc4
- Mã giả hàm khá ngắn:

- Điều mình chú ý ở đây là nó return về một hàm khác, bên cạnh đó thì biến `byte_2D41A8` cũng được thay đổi trong anti-debug nói trên nhưng mình đã patch rồi nên chẳng cần quan tâm lắm:

- Mình sẽ đổi tên một chút:

- Code khá đẹp rồi, chúng ta sẽ mô phỏng lại trong python:
```python=
def enc2(_input):
rc4Array = [0x89, 0xcd, 0x32, 0x41, 0x9a, 0x7c, 0xe5, 0x51, 0xf1, 0xc2, 0xa1, 0x76, 0x96, 0x59, 0x5f, 0x7a, 0x4f, 0x47, 0x88, 0x70, 0x4c, 0x63, 0x28, 0xa4, 0x21, 0x90, 0xea, 0x0, 0x9, 0xb0, 0x8f, 0x16, 0x3a, 0x8d, 0x3e, 0x9f, 0x8b, 0xe6, 0x74, 0x33, 0x40, 0xa2, 0xa8, 0x39, 0x2a, 0x36, 0xc7, 0x5b, 0xf0, 0xb4, 0xd7, 0x87, 0xde, 0xf7, 0x4a, 0x8a, 0x77, 0x30, 0x75, 0xaf, 0x94, 0x5a, 0xdf, 0x67, 0x48, 0xdd, 0x52, 0x93, 0xa3, 0x2f, 0xfe, 0xa6, 0x3, 0xd9, 0x4b, 0xc5, 0x5d, 0x62, 0x17, 0x66, 0xc6, 0x1e, 0xe4, 0xca, 0x46, 0x19, 0xd6, 0x92, 0x78, 0xec, 0xb5, 0x4d, 0xf4, 0xf2, 0x7b, 0x27, 0x8c, 0x31, 0xa9, 0x3b, 0x12, 0xaa, 0x73, 0x9d, 0x5, 0xe9, 0xb6, 0xab, 0xb, 0x8, 0x97, 0x7e, 0xba, 0x9e, 0x20, 0x25, 0x71, 0x38, 0x80, 0xe, 0x64, 0xeb, 0xe2, 0xcc, 0xfa, 0xce, 0xad, 0x44, 0x61, 0xf6, 0xff, 0x69, 0xd0, 0x13, 0x99, 0xfd, 0xda, 0x6b, 0x3c, 0x22, 0x56, 0x6e, 0xb2, 0x45, 0x26, 0x7f, 0xf8, 0xc, 0xbc,
0x1b, 0x6d, 0xc4, 0x42, 0xd8, 0x84, 0x72, 0xb3, 0x8e, 0x43, 0x1d, 0xb9, 0x5c, 0xbe, 0x5e, 0x53, 0x83, 0xcb, 0x85, 0x95, 0x9b, 0xae, 0xe8, 0x15, 0xb7, 0xd5, 0xe3, 0xbf, 0xcf, 0x6c, 0xd3, 0xc3, 0xc1, 0x14, 0xd, 0x1, 0xe1, 0xc9, 0x3f, 0xef, 0x18, 0x68, 0xa7, 0xe0, 0xc8, 0x2c, 0x86, 0x1f, 0xf5, 0xfb, 0x6a, 0xdb, 0x54, 0xd4, 0xbb, 0xd2, 0x49, 0x37, 0x23, 0xd1, 0xee, 0x2d, 0xac, 0x60, 0x4e, 0xe7, 0x79, 0x1c, 0xb1, 0x98, 0xf, 0x6f, 0x2, 0x7d, 0xa, 0xed, 0xa5, 0xa0, 0x6, 0x55, 0x24, 0x58, 0xc0, 0xbd, 0x91, 0x2b, 0xf3, 0x2e, 0x9c, 0x7, 0xdc, 0xf9, 0x29, 0x35,
0x4, 0x81, 0x57, 0x82, 0xb8, 0x50, 0x3d, 0x65, 0x11, 0x34, 0xfc, 0x10, 0x1a]
j = 0
k = 0
for i in range(32):
j = (j + 1) % 256
k = (k + rc4Array[j]) % 256
rc4Array[j], rc4Array[k] = rc4Array[k], rc4Array[j]
_input[i] = chr(ord(_input[i]) ^ (rc4Array[(rc4Array[j] + rc4Array[k]) % 256]))
return _input
```
- Kiểm tra:

với ida:

- Chính xác rồi, giờ chúng ta sẽ đi phân tích hàm `hook_enc2` kia:
```c=
void __stdcall hook_enc2()
{
DWORD CurrentProcessId; // eax
int v1; // [esp+10h] [ebp-288h]
int v2; // [esp+1Ch] [ebp-27Ch]
int v3; // [esp+20h] [ebp-278h]
int v4; // [esp+24h] [ebp-274h]
HANDLE hSnapshot; // [esp+28h] [ebp-270h]
char check; // [esp+57h] [ebp-241h]
PROCESSENTRY32W pe; // [esp+5Ch] [ebp-23Ch] BYREF
DWORD flOldProtect; // [esp+288h] [ebp-10h] BYREF
char Src[6]; // [esp+28Ch] [ebp-Ch] BYREF
check = 0;
GetCurrentProcessId();
CurrentProcessId = GetCurrentProcessId();
v1 = sub_2D11F0(CurrentProcessId);
memset(&pe, 0, sizeof(pe));
pe.dwSize = 556;
hSnapshot = CreateToolhelp32Snapshot(2u, 0);
if ( Process32FirstW(hSnapshot, &pe) )
{
while ( 1 )
{
if ( pe.th32ProcessID == v1 )
{
v4 = wcscmp(pe.szExeFile, toStr(aFlagchecker4Ex));
if ( v4 )
v4 = v4 < 0 ? -1 : 1;
if ( v4 )
{
v3 = wcscmp(pe.szExeFile, toStr(aCmdExe));
if ( v3 )
v3 = v3 < 0 ? -1 : 1;
if ( v3 )
{
v2 = wcscmp(pe.szExeFile, toStr(aExplorerExe));
if ( v2 )
v2 = v2 < 0 ? -1 : 1;
if ( v2 )
break;
}
}
}
if ( !Process32NextW(hSnapshot, &pe) )
goto LABEL_14;
}
check = 1;
}
LABEL_14:
if ( !check )
{
Src[0] = 104;
Src[5] = -61;
*&Src[1] = hook_enc3;
VirtualProtect(enc3_aes, 6u, 0x40u, &flOldProtect);
memcpy(enc3_aes, Src, 6u);
}
CloseHandle(hSnapshot);
}
```
- Chủ yếu mình chú ý tới duy nhất đoạn `VirtualProtect`, khả năng rất cao là hàm hook vào mới là hàm chuẩn.Để chứng minh, mình sẽ giải thích một chút:
- Toàn bộ code trước `while(1)` kia giúp chương trình lấy được `Parent Process` của nó và lưu thông tin vào `pe.szExeFile`, các bạn có thể đọc thêm tại [đây](https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/). Thông tin này được đem check với `aFlagchecker4Ex | aCmdExe | aExplorerExe`, nếu chương trình đang chạy nó không phải là một trong 3 thứ trên sẽ lập tức `CloseHandle()` và bỏ qua bước hook. Đây chính là cách mà chương trình nhận biết chúng ta có đang debug hay không, mình sẽ chứng minh bằng cách đặt bp vào dòng 27. `F9`, nhập `input` rồi `Tab` ra ngoài, dễ thấy:

- Do mình đang chạy bằng ida, rất nhanh chương trình sẽ đưa chúng ta tới đây:

- Vậy thì chúng ta sẽ tìm cách patch vào luồng đúng. Để ý một chút ở dòng 48 có `check = 1` và ở dòng 51 kiểm tra `if(!check)`, khi đó muốn vào luồng đúng chỉ cần patch cho `check = 0` ở là xong:

#### 10.3. enc3_aes and checker
- Đã có luồng chuẩn rồi, giờ chúng ta sẽ xem qua `hook_enc3` một chút:
```c=
int __usercall hook_enc3@<eax>(int a1@<ebp>, const void *a2)
{
int result; // eax
unsigned __int8 k; // [esp-47h] [ebp-53h]
unsigned __int8 j; // [esp-46h] [ebp-52h]
unsigned __int8 jj; // [esp-45h] [ebp-51h]
unsigned __int8 ii; // [esp-44h] [ebp-50h]
unsigned __int8 n; // [esp-43h] [ebp-4Fh]
unsigned __int8 m; // [esp-42h] [ebp-4Eh]
unsigned __int8 i; // [esp-41h] [ebp-4Dh]
__int128 v10; // [esp-40h] [ebp-4Ch] BYREF
_BYTE v11[36]; // [esp-24h] [ebp-30h] BYREF
int v12; // [esp+0h] [ebp-Ch]
int v13; // [esp+4h] [ebp-8h]
int retaddr; // [esp+Ch] [ebp+0h]
v12 = a1;
v13 = retaddr;
qmemcpy(v11, a2, 0x20u);
for ( i = 0; i < 0x20u; ++i )
v11[i] = (-120 - i) ^ i;
for ( j = 0; j < 0x20u; ++j )
v11[j] = sub_5D16B0(136, v11[j]);
v10 = xmmword_5D3180;
for ( k = 0; k < 0x58u; ++k )
{
sub_5D1630(v11, &v10);
sub_5D1630(&v11[16], &v10);
}
for ( m = 0; m < 0x20u; ++m )
*(a2 + m) = sub_5D1720(*(a2 + m), m & 7);
for ( n = 0; n < 0x20u; ++n )
*(a2 + n) ^= v11[n];
for ( ii = 0; ii < 0x20u; ++ii )
*(a2 + ii) = sub_5D1720(*(a2 + ii), 8 - (ii & 7));
for ( jj = 0; ; ++jj )
{
result = jj;
if ( jj >= 0x20u )
break;
*(a2 + jj) ^= -1 - v11[jj];
}
return result;
}
```
- Mình sẽ làm đẹp code một chút:

- Tuy nhiên, mình để ý sau mỗi lần debug, giá trị của `v10` và `v11` đều không thay đổi và `v10` được sử dụng cho bước mã hóa `input` (thực ra chúng là hai phần 16 bytes kề nhau đó: `v10 | v11`). Chính vì vậy mình sẽ đổi thành `constArray` và `Shift + E` lấy giá trị trong khi debug để đỡ phải decrypt aes:

- Hơi khó tìm chút nhưng được:
```python=
constArray = [0xa4, 0xa9, 0x1, 0xff, 0x22, 0xd3, 0xa3, 0x6, 0xde, 0x2c, 0x17, 0x81, 0xa6, 0x70, 0xa6, 0xe6, 0x7b, 0xb6, 0x47, 0x2, 0x7b, 0x8d, 0x2c, 0xc, 0x4a, 0x17, 0x21, 0x91, 0x60, 0x72, 0x8, 0xe4]
```
- Tương tự như trên, chúng ta sẽ mô phỏng thuật toán trong python, tất nhiên là từ dòng 29 trở đi vì `constArray` mình đã lấy được mà không cần tính toán gì rồi:
```python=
def ROL(a1, a2):
return (a1 >> (8 - a2)) | (a1 << a2)
def enc3(_input):
constArray = [0xa4, 0xa9, 0x1, 0xff, 0x22, 0xd3, 0xa3, 0x6, 0xde, 0x2c, 0x17, 0x81, 0xa6, 0x70, 0xa6, 0xe6, 0x7b, 0xb6, 0x47, 0x2, 0x7b, 0x8d, 0x2c, 0xc, 0x4a, 0x17, 0x21, 0x91, 0x60, 0x72, 0x8, 0xe4]
for m in range(32):
_input[m] = ROL(ord(_input[m]), m & 7) % 256
_input[m] = chr(_input[m])
for n in range(32):
_input[n] = (ord(_input[n]) ^ constArray[n]) % 256
_input[n] = chr(_input[n])
for ii in range(32):
_input[ii] = ROL(ord(_input[ii]), 8 - (ii & 7)) % 256
_input[ii] = chr(_input[ii])
for jj in range(32):
_input[jj] = (ord(_input[jj]) ^ (-1 - constArray[jj])) % 256
_input[jj] = chr(_input[jj])
return _input
```
- Lại kiểm tra:

với ida:

- Ye, vậy là xong tất cả, kế đó chúng ta sẽ check với `flag_checker` của bài:

- Trích lấy data này và reverse lại thuật toán, chúng ta dễ dàng lấy lại được flag bằng brute force:
```python=
flag_checker = [0x9c, 0x87, 0x9c, 0x6e, 0x64, 0x27, 0x3b, 0x78, 0x71, 0x53, 0x2b, 0x6d, 0xd4, 0xe, 0x82, 0x22, 0x5d, 0xc4, 0xe2, 0xe8, 0x7, 0xb9, 0x85, 0xa7, 0x49, 0x9a, 0x6d, 0xd4, 0xfc, 0x64, 0xba, 0x2]
flag = ""
for _ in range(32):
for c in range(0x20, 0x7f):
test = [ord(x) for x in flag + chr(c)] + [0] * (31 - len(flag))
test = enc3(enc2(enc1(test)))
if ord(test[len(flag)]) == flag_checker[len(flag)]:
flag += chr(c)
break
print(flag)
```
- Nhận được:

- Tuy nhiên, tên bài là `Lies`, vốn dĩ chẳng cần thuật toán giải mã gì cả, author đã cố tình viết thuật toán trên thành mã hóa đối xứng, và chúng ta có:

- Rất bất ngờ phải không, đây chính là lí do mình gọi tác giả là `noobmannn and his borderland` vì có quá nhiều cú twist trong bài. Một lần nữa cảm ơn anh vì những thuật toán mã hóa đối xứng có một không hai này :bow_and_arrow:.
>~~`KCSC{6r347!!!y0u_4r3_w1nn3r:333}`~~
#### 10.4. fake flag
- Note, nếu các bạn muốn tìm fake flag, có thể ngồi reverse với phong cách tự vả anti-debug vào mặt :penguin:, code mình (có tham khảo từ [code anh TungDvan](
) và thuật toán của `aesenc`, `aesenclast` trong [link](https://github.com/noobmannn/CTF_WriteUp/blob/main/AES_NI.md)) như sau:
```python=
from copy import deepcopy as copy
sboxInv = [
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
]
def transpose4x4(m):
return m[0::4] + m[1::4] + m[2::4] + m[3::4]
def list2hex(list):
list = list[::-1]
hex = ""
for e in list:
hex += "{:02X}".format(e)
return hex
def hex2list(hex):
byte_list = [hex[i:i+2] for i in range(0, len(hex), 2)][::-1]
hex = ''.join(byte_list)
lst = []
if len(hex) % 2 == 0:
for i in range(len(hex)//2):
lst.append(int(hex[i*2:i*2+2], 16))
return lst
def xor(bytelist1, bytelist2):
res = []
length = min(len(bytelist1), len(bytelist2))
for i in range(length):
res.append(bytelist1[i] ^ bytelist2[i])
return res
def aesdec_cal(state, roundkey, last=False):
def rotate(word, n):
return word[n:]+word[0:n]
def shift_rows_inv(state):
for i in range(4):
state[i*4:i*4+4] = rotate(state[i*4:i*4+4],-i)
def sub_bytes_inv(state):
for i in range(16):
state[i] = sboxInv[state[i]]
def galoisMult(a, b):
p = 0
hiBitSet = 0
for i in range(8):
if b & 1 == 1:
p ^= a
hiBitSet = a & 0x80
a <<= 1
if hiBitSet == 0x80:
a ^= 0x1b
b >>= 1
return p % 256
def mixColumnInv(column):
temp = copy(column)
column[0] = galoisMult(temp[0],14) ^ galoisMult(temp[3],9) ^ \
galoisMult(temp[2],13) ^ galoisMult(temp[1],11)
column[1] = galoisMult(temp[1],14) ^ galoisMult(temp[0],9) ^ \
galoisMult(temp[3],13) ^ galoisMult(temp[2],11)
column[2] = galoisMult(temp[2],14) ^ galoisMult(temp[1],9) ^ \
galoisMult(temp[0],13) ^ galoisMult(temp[3],11)
column[3] = galoisMult(temp[3],14) ^ galoisMult(temp[2],9) ^ \
galoisMult(temp[1],13) ^ galoisMult(temp[0],11)
return column
def mix_columns_inv(data):
new = bytearray(16)
for i in range(4):
column = [data[i], data[i+4], data[i+8], data[i+12]]
column = mixColumnInv(column)
data[i], data[i+4], data[i+8], data[i+12] = column[0], column[1], column[2], column[3]
# new[i*4: i*4+4] = column[0], column[1], column[2], column[3]
return data
state = xor(state, roundkey)
if not last:
state = mix_columns_inv(state)
shift_rows_inv(state)
sub_bytes_inv(state)
return state
def aesdec(dat, k):
data = transpose4x4(hex2list(dat.hex()))
key = transpose4x4(hex2list(k.hex()))
res = transpose4x4(aesdec_cal(data, key))
return bytes.fromhex(list2hex(res))
def aesdeclast(dat, k):
data = transpose4x4(hex2list(dat.hex()))
key = transpose4x4(hex2list(k.hex()))
res = transpose4x4(aesdec_cal(data, key, last=True))
return bytes.fromhex(list2hex(res))
def enc1(_input):
for i in range(len(_input)):
_input[i] ^= 0xAB
for i in range(len(_input)):
_input[i] ^= i - 85
for i in range(0, len(_input), 4):
_input[i] ^= 0xC0
_input[i+1] ^= 0xFE
_input[i+2] ^= 0xBE
_input[i+3] ^= 0xEF
for i in range(0, len(_input), 4):
_input[i] ^= 0xDE
_input[i+1] ^= 0xAD
_input[i+2] ^= 0xBA
_input[i+3] ^= 0xBE
for i in range(len(_input)):
_input[i] ^= 0xCD
for i in range(len(_input)):
_input[i] ^= i - 51
for i in range(0, len(_input), 4):
_input[i] ^= 0xC0
_input[i+1] ^= 0xFE
_input[i+2] ^= 0xBA
_input[i+3] ^= 0xBE
for i in range(0, len(_input), 4):
_input[i] ^= 0xDE
_input[i+1] ^= 0xAD
_input[i+2] ^= 0xBE
_input[i+3] ^= 0xEF
for i in range(len(_input)):
_input[i] ^= 0xEF
for i in range(len(_input)):
_input[i] ^= i - 17
_input = [chr(x % 256) for x in _input]
return _input
def enc2(_input):
sbox = [0x89, 0x49, 0x1a, 0x48, 0x98, 0xd6, 0xaf, 0x56, 0xce, 0x67,
0xed, 0x16, 0x2e, 0x2f, 0x53, 0x8f, 0x08, 0xc3, 0x94, 0x15,
0x50, 0x4d, 0x69, 0x33, 0x03, 0x1c, 0x2c, 0xea, 0xbb, 0xcc,
0x57, 0x7c, 0x59, 0x8d, 0x3e, 0x9f, 0x8b, 0xe6, 0x74, 0xa4,
0x40, 0xa2, 0xa8, 0x39, 0x2a, 0x36, 0xc7, 0x5b, 0xf0, 0xb4,
0xd7, 0x87, 0xde, 0xf7, 0x4a, 0x8a, 0x77, 0x30, 0x75, 0xe5,
0x88, 0x5a, 0xdf, 0xc2, 0x41, 0xdd, 0x52, 0x93, 0xa3, 0x3a,
0xfe, 0xa6, 0x21, 0xd9, 0x4b, 0xc5, 0x5d, 0x62, 0x17, 0x66,
0xc6, 0x1e, 0xe4, 0xca, 0x46, 0x19, 0x76, 0x92, 0x78, 0xec,
0xb5, 0x63, 0xf4, 0xf2, 0x7b, 0x27, 0x8c, 0x31, 0xa9, 0x3b,
0x12, 0xaa, 0x73, 0x9d, 0x05, 0xe9, 0xb6, 0xab, 0x0b, 0x4f,
0x97, 0x7e, 0xba, 0x9e, 0x20, 0x25, 0x71, 0x38, 0x80, 0x0e,
0x64, 0xeb, 0xe2, 0xb0, 0xfa, 0xf1, 0xad, 0x44, 0x61, 0xf6,
0xff, 0x28, 0xd0, 0x13, 0x99, 0xfd, 0xda, 0x6b, 0x3c, 0x22,
0x51, 0x6e, 0xb2, 0x45, 0x26, 0x7f, 0xf8, 0x0c, 0xbc, 0x1b,
0x6d, 0xc4, 0x42, 0xd8, 0x84, 0x72, 0xb3, 0x8e, 0x43, 0x1d,
0xb9, 0x5c, 0xbe, 0x5e, 0x5f, 0x83, 0xcb, 0x85, 0x95, 0x9b,
0xae, 0xe8, 0x70, 0xb7, 0xd5, 0xe3, 0xbf, 0xcf, 0x6c, 0xd3,
0x47, 0xc1, 0x14, 0x0d, 0x01, 0xe1, 0xc9, 0x3f, 0xef, 0x18,
0x68, 0xa7, 0xe0, 0xc8, 0x00, 0x86, 0x1f, 0xf5, 0xfb, 0x6a,
0xdb, 0x54, 0xd4, 0x09, 0xd2, 0xcd, 0x37, 0x23, 0xd1, 0xee,
0x2d, 0xac, 0x60, 0x4e, 0xe7, 0x79, 0x90, 0xb1, 0x9a, 0x0f,
0x6f, 0x02, 0x7d, 0x0a, 0xa1, 0xa5, 0xa0, 0x06, 0x55, 0x24,
0x58, 0xc0, 0xbd, 0x91, 0x2b, 0xf3, 0x96, 0x9c, 0x07, 0xdc,
0xf9, 0x29, 0x35, 0x04, 0x81, 0x7a, 0x82, 0xb8, 0x4c, 0x3d,
0x65, 0x11, 0x34, 0xfc, 0x10, 0x32]
j = 0
k = 0
for i in range(32):
j = (j + 1) % 256
k = (k + sbox[j]) % 256
sbox[j], sbox[k] = sbox[k], sbox[j]
_input[i] = chr(ord(_input[i]) ^ (sbox[(sbox[j] + sbox[k]) % 256]))
return _input
def troller(flag, key):
flag = list(aesdec(aesdeclast(flag[::-1], key), key)[::-1])
for i in range(16):
flag[i] ^= KCSC[i]
return bytes(flag)
flag_en = [
0x9c, 0x87, 0x9c, 0x6e, 0x64, 0x27, 0x3b, 0x78, 0x71, 0x53,
0x2b, 0x6d, 0xd4, 0x0e, 0x82, 0x22, 0x5d, 0xc4, 0xe2, 0xe8,
0x07, 0xb9, 0x85, 0xa7, 0x49, 0x9a, 0x6d, 0xd4, 0xfc, 0x64,
0xba, 0x02
]
KCSC = [
0x43, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x43, 0x00,
0x00, 0x00, 0x4B, 0x00, 0x00, 0x00
]
xorxor = [
0x1D, 0x97, 0x2B, 0x75, 0x6B, 0x11, 0xA2, 0xA5, 0xEC, 0x95,
0x5C, 0x49, 0xE6, 0x04, 0x33, 0x92, 0xEC, 0x44, 0xA1, 0x2A,
0xEC, 0x61, 0x46, 0x88, 0xC8, 0x49, 0xE8, 0x6D, 0x75, 0xD7,
0xF7, 0x20
]
for i in range(len(flag_en)):
flag_en[i] ^= xorxor[i]
flag_en = bytes(flag_en)
key = bytes(KCSC)[::-1]
flag = troller(flag_en[:16], key) + troller(flag_en[16:], key)
flag = [x for x in flag]
print(''.join(enc2(enc1(flag))))
```
>`KCSC{y0u_5h0uldn'7_5ubm17_m3!!!}`
## III. End
- Và trên đây chính là toàn bộ lời giải của mình cho mảng reverse của TTV24, mong rằng giải sau sẽ chất lượng như (hoặc hơn) giải nay. Bài viết của mình có tham khảo từ [wu của anh TungDvan](https://github.com/DecemberRecruitment/SSB3YW50IHRvIGJlY29tZSBhIG1lbWJlciBvZiB0aGUgS0NTQyBjbHViLg-/tree/master/KCSC_RECRUITMENT_2024), các bạn có thể tìm đọc nhé. Hi vọng bài viết này hữu ích với các bạn. Dear!!!