---
# System prepended metadata

title: KCSC RECRUITMENT 2026 ONLY RE

---

# LLM 
Đọc tên đề thì hiểu luôn bài là dạng Linked List gì đó. Chạy thử thì bài bắt ta nhập 3 Key (0 1 2). 

Nạp vào IDA xem thử: 
```cpp!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int count; // [rsp+20h] [rbp-78h]
  char *v5; // [rsp+28h] [rbp-70h]
  _BYTE *Block; // [rsp+30h] [rbp-68h]
  __int64 v7; // [rsp+48h] [rbp-50h]
  __int64 v8; // [rsp+50h] [rbp-48h]
  char *v9; // [rsp+58h] [rbp-40h] BYREF
  char *v10; // [rsp+60h] [rbp-38h] BYREF
  char *v11; // [rsp+68h] [rbp-30h] BYREF
  char *v12; // [rsp+70h] [rbp-28h] BYREF
  char v13[8]; // [rsp+78h] [rbp-20h] BYREF

  v10 = 0i64;                                   // null
  v11 = 0i64;                                   // null
  v9 = 0i64;                                    // null
  v12 = 0i64;                                   // null
  printf("Enter key to get flag!\n");
  v5 = data;
  count = 0;
  while ( v5 )                                  // đoạn này ta nhập key 3 lần (key 0  1 2)
  {
    printf("Key%d : ", count);
    scanf("%d", v13);
    add_node(&v10, &v11, v13[0], v5[9]);
    v5 = *(v5 + 10);
    ++count;
  }
  Congdathuc(data, v10, &v9, &v12);
  v7 = Calculate(v9, 1337i64);
  v8 = Calculate(v9, 9999i64);
  if ( v7 == 0xD427202CB4B2i64 )
  {
    printf("Correct!\n");
    Block = malloc(0x31ui64);
    sub_7FF637791490(&unk_7FF637794078, 0x30ui64, v8, Block);
    Block[48] = 0;
    printf("Flag: %s\n", Block);
    free(Block);
  }
  else
  {
    printf("Wrong!\n");
  }
  return 0;
}
```

Cùng phân tích nào: 
- Ta được cho sẵn v5, mở IDA ra và thấy là dường như nó là 1 cái LinkedList đôi. Đề bài này coi LinkedList là 1 cách biểu diễn đa thức, đa thức `v5` sau khi dump data thì ta biết được nó là đa thức có dạng sau: 
$$
36⋅x^4+18⋅x^2+7
$$ 
- Đồng thời có luôn cả format Node: 
```
struct Node {
    struct Node *prev;   
    uint8_t coef;        
    uint8_t deg;      
    struct Node *next;    
};
```
- Tiếp theo, chương trình lấy giá trị key của mình nhập làm tham số cho add_node. Tham số deg là lấy của v5 100% (tức là các số mũ như nhau)
- Thực hiện các phép toán dựa trên LinkedList rồi check với Kết Qủa có sẵn. 

Cùng nhau mổ xẻ các hàm quan trọng nhé: 
```cpp!
char **__fastcall add_node(char **tail, char **top, char a1, char a2)
{
  char **result; // rax
  char *v5; // [rsp+20h] [rbp-18h]

  v5 = makenode(a1, a2);
  if ( *tail ) 
  {  // Đã có tail
    *(*top + 10) = v5;
    *v5 = *top;
    result = top;
    *top = v5;
  }
  else
  { // Null List
    *top = v5;
    result = tail;
    *tail = v5;
  }
  return result;
}
``` 
Theo Logic thẻ bản chất hàm add node trông đơn giản như này thôi:
```
0 node
tail ──┐
      node
top  ──┘


1 node
tail -> [A] <-> [B] <-> [C] <- top
```
`make_node`: Đây làm sinh 1 node (1 phần tử của đa thức):
```cpp!
char *__fastcall makenode(char a1, char a2)
{
  char *v3; // [rsp+20h] [rbp-18h]

  v3 = malloc(0x12ui64);
  if ( !v3 )
    return 0i64;
  v3[8] = a1;                                   // coef
  v3[9] = a2;                                   // deg
  *v3 = 0i64;                                   // prev
  *(v3 + 10) = 0i64;                            // next
  return v3;
}
```

Vậy với vòng lặp chạy 3 lần (do `v5` có 3 Node), chương trình nhảy xuống hàm Congdathuc: 
```cpp!
__int64 __fastcall Congdathuc(__int64 a1, __int64 a2, char **a3, char **a4)
{
  __int64 result; // rax List mới cầm kqua

  while ( a1 || a2 )
  {
    if ( a2 && (!a1 || *(a1 + 9) >= *(a2 + 9)) )
    {
      if ( a1 && *(a2 + 9) >= *(a1 + 9) )       // đề cho như kia thì chỉ nhảy vào trường hợp degree bằng nhau này thôi
      {
        add_node(a3, a4, *(a2 + 8) + *(a1 + 8), *(a1 + 9));// Đưa các kết quả (Tổng hệ số) các phần tử 
        a1 = *(a1 + 10);
        result = *(a2 + 10);
        a2 = result;
      }
      else
      {
        add_node(a3, a4, *(a2 + 8), *(a2 + 9));
        result = *(a2 + 10);
        a2 = result;
      }
    }
    else
    {
      add_node(a3, a4, *(a1 + 8), *(a1 + 9));
      result = *(a1 + 10);
      a1 = result;
    }
  }
  return result;
}
```
Sau khi cộng xong thì nhảy vào Calculator để tính giá trị của đa thức mới ta cóa :D
```cpp!
__int64 __fastcall sub_7FF6377913F0(__int64 a1, __int64 a2)
{
  int i; // [rsp+0h] [rbp-28h]
  __int64 v4; // [rsp+8h] [rbp-20h]
  __int64 v5; // [rsp+10h] [rbp-18h]

  v5 = 0i64;
  while ( a1 )
  {
    v4 = 1i64;
    for ( i = 0; i < *(a1 + 9); ++i )           // coef*x cộng với nhay cho đến khi hết deg
      v4 *= a2; //a2 = x
    v5 += v4 * *(a1 + 8);
    a1 = *(a1 + 10);
  }
  return v5;
}
```

Vậy nếu như Đa thức: 
$$
(36 + k0)⋅x^4+(18+k1)⋅x^2+7+k0
$$ 

Ta nhập cộng với Đa thức `v5` mà khi thay `x = 1337` mà nhận được Kết quả: `0xD427202CB4B2i64` thì nó sẽ sinh khóa bằng `Calculate` với x = 9999 để giải mã flag: 

Ta brute luôn cho nhanh: 
```python!
TARGET = 0xD427202CB4B2
x = 1337
x2 = pow(x, 2)
x4 = pow(x, 4)
for k0 in range(256):
    part0 = (36 + k0) * pow(x, 4)
    rem1 = TARGET - part0
    if rem1 < 0:
        continue
    for k1 in range(256):
        part1 = (18 + k1) * pow(x, 2)
        rem2 = rem1 - part1
        if rem2 < 18:
            continue
        k2 = rem2 - 7
        if 0 <= k2 <= 255:
            print(k0, k1, k2)
```
Flag: **KCSC{You_have_passed_the_DSA_final_exam!!!}**
# PuRE (easy): 
Ném file vào IDA, ta được đưa đến main là 1 Puzzle Game (mình đổi tên vài hàm cho dễ quan sát): 
```cpp!
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char v4[256]; // [rsp+0h] [rbp-710h] BYREF
  char dest[512]; // [rsp+100h] [rbp-610h] BYREF
  char s[1008]; // [rsp+300h] [rbp-410h] BYREF
  __int64 v7; // [rsp+6F0h] [rbp-20h]
  unsigned int seed; // [rsp+6F8h] [rbp-18h]
  int v9; // [rsp+6FCh] [rbp-14h]
  char *v10; // [rsp+700h] [rbp-10h]
  __int64 i; // [rsp+708h] [rbp-8h]

  seed = time(0LL);
  srand(seed);
  puts("=======================================================");
  puts("=                 PUZZLE CTF CHALLENGE                =");
  puts("=======================================================");
  reset();
  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 ( dword_55555555EFA0 )
          moves_reversed();
        else
          puts("[!] Doc code chua ma go command linh tinh?");
      }
      else if ( !strcmp(dest, "test") )
      {
        reset();
        Flow_check = 0;
        Scramble_and_Store(36LL);
        show_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 ( dword_55555555A16C )
        {
          puts("\n[!] Starting REAL CHALLENGE...");
          puts("[!] Scrambling 1836 times. Du trinh ko???\n");
          reset();
          puts("[!] Reset challenge...Done\n");
          Flow_check = 1;
          Scramble_and_Store(1836LL);
        }
        else
        {
          puts("\n[!] You must complete TEST mode first!");
        }
      }
      else if ( dword_55555555EFA0 )
      {
        v7 = 0LL;
        v10 = strtok(s, " ");
        v9 = 0;
        while ( v10 )
        {
          if ( (unsigned int)sub_555555556169(v10) )
            ++v9;
          else
            printf("[!] Unknown: %s\n", v10);
          v10 = strtok(0LL, " ");
        }
        if ( v9 > 0 )
        {
          if ( (unsigned int)sub_555555556894() )
          {
            putchar(10);
            puts("================================================");
            puts("=      CONGRATULATIONS! CHALLENGE SOLVED!      =");
            puts("================================================");
            if ( Flow_check )
            {
              sub_555555555219(v4, 256LL);
              printf("  FLAG: %s\n", v4);
              printf("  Total moves: %d\n", (unsigned int)dword_55555555A164);
              putchar(10);
              puts("  You are a TRUE Rot's Master!");
            }
            else
            {
              putchar(10);
              puts("  Nice! You solved the practice cube!");
              printf("  Total moves: %d\n", (unsigned int)dword_55555555A164);
              putchar(10);
              puts("  >> You can now access CHALLENGE mode!");
              puts("  >> Type 'challenge' for the REAL challenge!");
              dword_55555555A16C = 1;
            }
            puts("================================================");
            if ( Flow_check )
              return 0LL;
            reset();
            Flow_check = 0;
            dword_55555555EFA0 = 0;
          }
          else
          {
            if ( dword_55555555A164 > 998 )
            {
              puts("\n[!] Too many moves! This puzzle is not that kind of ez=)))");
              return 0xFFFFFFFFLL;
            }
            printf("Moves: %d\n", (unsigned int)dword_55555555A164);
          }
        }
      }
      else
      {
        puts("[!] Doc code chua ma go tinh tinh zay");
      }
    }
  }
  return 0LL;
}
```
`Scramble and Store`: 
```cpp!
__int64 __fastcall sub_555555556391(int a1)
{
  __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 ( a1 > 50 )
  {
    printf("[!] Scrambling with % d moves", (unsigned int)a1);
    for ( i = 0; i < a1; ++i )
    {
      v4 = rand() % 12;
      strcpy((char *)&res + 10 * i, src[v4]);
      Scramble(src[v4]);
      if ( !(i % 100) )
        putchar(46);
    }
    puts(" Done!");
  }
  else
  {
    printf("[!] Scramble (%d moves): ", (unsigned int)a1);
    for ( j = 0; j < a1; ++j )
    {
      v3 = rand() % 12;
      strcpy((char *)&res + 10 * j, src[v3]);
      Scramble(src[v3]);
      printf("%s ", src[v3]);
    }
    putchar(10);
  }
  result = (unsigned int)a1;
  dword_55555555EFA0 = a1;
  dword_55555555A164 = 0;
  return result;
}
```
Đây là bên trong `Scamble and Store`, có vẻ như đề bài lấy seed theo thời gian, tạo `srand()` theo seed đó và từ việc gọi `rand()` sẽ chọn 1 cách tráo rubik ngẫu nhiên. Khi gọi help, ta được ném cho seed và khi gọi test ta được ném cho 1 challenge warm-up nhỏ là giải khối rubik bị tráo 36 lần và cuối cùng khi gọi `challenge` ta sẽ phải giải khối được tráo là `1836` lần tráo: 

Flow bắt buộc của đề là: `help -> test -> challenge -> Flag`. Dường như ta chỉ cần chạy qua `test` bằng cách ném Scramble cho 1 con bot giải Rubik nào đó trên mạng: 

![Screenshot 2025-12-15 103431](https://hackmd.io/_uploads/ByfjfW6GWe.png)
![image](https://hackmd.io/_uploads/BJ42GWaz-x.png)

Rồi giải `challenge` bằng cách sử dụng gọi `solution` là xong:
![image](https://hackmd.io/_uploads/BkJyQZpG-l.png)


nhưng không :v 
```cpp!
if ( dword_55555555A164 > 998 )
            {
              puts("\n[!] Too many moves! This puzzle is not that kind of ez=)))"); // Move phải dưới 999, Trong khi `moves_reversed` sẽ đưa ta hơn 1 nghìn moves :(((
```
Trong Python có 1 cái thư viện là `kociemba` giúp hỗ trợ giải nhanh rubik với số moves ít nhất có thể. 
> Chúng ta đã từng chơi Rubik cũng hiểu rằng bản chất có thể rút ngắn số moves được như vậy là do có những lệnh tráo vô nghĩa (trái rồi xoay phải ngược lại = ban đầu). 1836 lần Scramble random này hoàn toàn có thể rút gọn để ra được lời giải tối ưu!. 

Solution using `kociemba`:
```python!
from pwn import *
import kociemba
import re

HOST = '67.223.119.69'
PORT = 5025

MOVES_MAP = ["U", "U'", "D", "D'", "L", "L'", "R", "R'", "F", "F'", "B", "B'"]
inverse_map = { "U": "U'", "U'": "U", "D": "D'", "D'": "D", "L": "L'", "L'": "L", "R": "R'", "R'": "R", "F": "F'", "F'": "F", "B": "B'", "B'": "B" } # Cặp đối Moves

# Đoạn test mình dùng Python lấy srand() của ctypes nhưng lạ là rand() bị lệch nên mình nhờ Gemini gen 1 cái rand() dường như chuẩn xác hơn
class GlibcRand:
    def __init__(self):
        self.r = [0] * 344
        self.idx = 0
    def srand(self, seed):
        self.r[0] = seed
        for i in range(1, 31):
            self.r[i] = (16807 * self.r[i-1]) % 2147483647
        if self.r[0] < 0: self.r[0] += 2147483647
        for i in range(31, 34):
            self.r[i] = self.r[i-31]
        for i in range(34, 344):
            self.r[i] = (self.r[i-31] + self.r[i-3]) & 0xFFFFFFFF
        self.idx = 0
    def rand(self):
        ptr_3 = (self.idx + 344 - 3) % 344
        ptr_31 = (self.idx + 344 - 31) % 344
        val = (self.r[ptr_3] + self.r[ptr_31]) & 0xFFFFFFFF
        self.r[self.idx] = val
        self.idx = (self.idx + 1) % 344
        return val >> 1

# Mô phỏng rubik genbygipity :<<
class ServerCube:
    def __init__(self):
        # 6 faces * 9 stickers. Order: U(0), D(1), L(2), R(3), F(4), B(5)
        # Colors: W, Y, O, R, G, B
        self.state = ['W']*9 + ['Y']*9 + ['O']*9 + ['R']*9 + ['G']*9 + ['B']*9
        # Faces mapping
        self.U, self.D, self.L, self.R, self.F, self.B = 0, 1, 2, 3, 4, 5
    
    def rotate_face_cw(self, face_idx):
        # Rotate 3x3 face clockwise
        b = self.state[face_idx*9 : (face_idx+1)*9]
        # 0 1 2    6 3 0
        # 3 4 5 -> 7 4 1
        # 6 7 8    8 5 2
        new_b = [b[6], b[3], b[0], b[7], b[4], b[1], b[8], b[5], b[2]]
        self.state[face_idx*9 : (face_idx+1)*9] = new_b

    def rotate_face_ccw(self, face_idx):
        # Counter-clockwise (3 CW rotations)
        for _ in range(3): self.rotate_face_cw(face_idx)

    def apply_move(self, move):
        # Mapping logic verified against server ASM
        if move == 'U':
            self.rotate_face_cw(0) # Face U
            # Cycle rows: F(4) <- R(3) <- B(5) <- L(2) <- F(4)
            # Indices: 0,1,2 of each face
            self._cycle_rows(4, 3, 5, 2, [0,1,2])
        elif move == "U'":
            self.rotate_face_ccw(0)
            self._cycle_rows(2, 5, 3, 4, [0,1,2]) # Reverse U
        elif move == 'D':
            self.rotate_face_cw(1) # Face D
            # Cycle rows: F(4) <- L(2) <- B(5) <- R(3) <- F(4)
            # Indices: 6,7,8 (Bottom row)
            self._cycle_rows(4, 2, 5, 3, [6,7,8])
        elif move == "D'":
            self.rotate_face_ccw(1)
            self._cycle_rows(3, 5, 2, 4, [6,7,8])
        elif move == 'L':
            self.rotate_face_cw(2) # Face L
            # Cycle cols: U(0) <- B(5) <- D(1) <- F(4) <- U(0)
            # Note: B orientation is tricky. Server logic:
            # U left col (0,3,6) gets B right col (8,5,2)? Or B left col?
            # Standard L: U(Left) <- B(RightInv) <- D(Left) <- F(Left)
            # Let's use generic simulation of standard move logic
            self._cycle_col_special_L(True)
        elif move == "L'":
            self.rotate_face_ccw(2)
            self._cycle_col_special_L(False)
        elif move == 'R':
            self.rotate_face_cw(3) # Face R
            self._cycle_col_special_R(True)
        elif move == "R'":
            self.rotate_face_ccw(3)
            self._cycle_col_special_R(False)
        elif move == 'F':
            self.rotate_face_cw(4) # Face F
            # Cycle: U(Bot) <- L(Right) <- D(Top) <- R(Left)
            self._cycle_ring_F(True)
        elif move == "F'":
            self.rotate_face_ccw(4)
            self._cycle_ring_F(False)
        elif move == 'B':
            self.rotate_face_cw(5) # Face B
            self._cycle_ring_B(True)
        elif move == "B'":
            self.rotate_face_ccw(5)
            self._cycle_ring_B(False)

    def _cycle_rows(self, f1, f2, f3, f4, idxs):
        # f1 gets f2, f2 gets f3, f3 gets f4, f4 gets f1
        for i in idxs:
            temp = self.state[f1*9 + i]
            self.state[f1*9 + i] = self.state[f2*9 + i]
            self.state[f2*9 + i] = self.state[f3*9 + i]
            self.state[f3*9 + i] = self.state[f4*9 + i]
            self.state[f4*9 + i] = temp

    def _cycle_col_special_L(self, cw):
        # U(0,3,6), F(0,3,6), D(0,3,6), B(8,5,2)
        u = [0,3,6]; f = [0,3,6]; d = [0,3,6]; b = [8,5,2]
        if cw: # U <- B <- D <- F <- U
            self._cycle_4_arrays(0, 5, 1, 4, u, b, d, f)
        else:  # U <- F <- D <- B <- U
            self._cycle_4_arrays(0, 4, 1, 5, u, f, d, b)

    def _cycle_col_special_R(self, cw):
        # U(2,5,8), B(6,3,0), D(2,5,8), F(2,5,8)
        u = [2,5,8]; b = [6,3,0]; d = [2,5,8]; f = [2,5,8]
        if cw: # U <- F <- D <- B <- U
             self._cycle_4_arrays(0, 4, 1, 5, u, f, d, b)
        else:
             self._cycle_4_arrays(0, 5, 1, 4, u, b, d, f)

    def _cycle_ring_F(self, cw):
        # U(6,7,8), R(0,3,6), D(2,1,0), L(8,5,2)
        u = [6,7,8]; r = [0,3,6]; d = [2,1,0]; l = [8,5,2]
        if cw: # U <- L <- D <- R <- U
            self._cycle_4_arrays(0, 2, 1, 3, u, l, d, r)
        else:
            self._cycle_4_arrays(0, 3, 1, 2, u, r, d, l)

    def _cycle_ring_B(self, cw):
        # U(2,1,0), L(0,3,6), D(6,7,8), R(8,5,2)
        u = [2,1,0]; l = [0,3,6]; d = [6,7,8]; r = [8,5,2]
        if cw: # U <- R <- D <- L <- U
            self._cycle_4_arrays(0, 3, 1, 2, u, r, d, l)
        else:
            self._cycle_4_arrays(0, 2, 1, 3, u, l, d, r)

    def _cycle_4_arrays(self, f1, f2, f3, f4, idx1, idx2, idx3, idx4):
        # f1[idx1] gets f2[idx2], etc...
        for k in range(3):
            t = self.state[f1*9 + idx1[k]]
            self.state[f1*9 + idx1[k]] = self.state[f2*9 + idx2[k]]
            self.state[f2*9 + idx2[k]] = self.state[f3*9 + idx3[k]]
            self.state[f3*9 + idx3[k]] = self.state[f4*9 + idx4[k]]
            self.state[f4*9 + idx4[k]] = t

    def to_kociemba(self):
        cmap = {'W':'U', 'R':'R', 'G':'F', 'Y':'D', 'O':'L', 'B':'B'}
        k_str = ""
        # Face U
        for i in range(9): k_str += cmap[self.state[0*9 + i]]
        # Face R
        for i in range(9): k_str += cmap[self.state[3*9 + i]]
        # Face F
        for i in range(9): k_str += cmap[self.state[4*9 + i]]
        # Face D
        for i in range(9): k_str += cmap[self.state[1*9 + i]]
        # Face L
        for i in range(9): k_str += cmap[self.state[2*9 + i]]
        # Face B
        for i in range(9): k_str += cmap[self.state[5*9 + i]]
        
        return k_str

def main():
    r = remote(HOST, PORT)
    print("Testing")
    r.recvuntil(b'>> ')
    r.sendline(b'test')
    r.recvuntil(b'Scramble (36 moves): ')
    scramble_line = r.recvline().strip().decode()
    r.recvuntil(b'>> ') 
    moves = scramble_line.split()
    sol_moves = [inverse_map[m] for m in moves[::-1]] # ánh xạ đảo ngược các Moves nhận được
    r.sendline(" ".join(sol_moves).encode())
    
    if "Nice!" in r.recvuntil(b'Nice!').decode():
        print("Ngon! Test ok")
    r.recvuntil(b'REAL challenge!')
    r.recvuntil(b'>> ')

    r.sendline(b'help')
    res_help = r.recvuntil(b'>> ').decode()
    match = re.search(r'(0x[0-9a-fA-F]+)', res_help)
    if not match: 
        return
    SEED = int(match.group(1), 16)
    print(hex(SEED))
    print('Final Solve')
    r.sendline(b'challenge')
    
    # 1. GENERATE MOVES (GlibcRand)
    rng = GlibcRand()
    rng.srand(SEED)
    print("[*] Burning 36 moves...")
    for _ in range(36): rng.rand()
    
    print("[*] Generating 1836 moves...")
    challenge_moves = []
    for _ in range(1836):
        val = rng.rand()
        challenge_moves.append(MOVES_MAP[val % 12])
        
    # 2. APPLY TO CUSTOM CUBE (No Pycuber)
    print("[*] Applying moves to Simulator...")
    cube = ServerCube()
    for m in challenge_moves:
        cube.apply_move(m)
        
    # 3. SOLVE
    state_str = cube.to_kociemba()
    print(f"[*] Kociemba State: {state_str}")
    
    # Sanity check: Center of U face (index 4) MUST be U
    if state_str[4] != 'U':
        print("[!] WARNING: Center of U is NOT U. Logic error likely.")
    
    raw_sol = kociemba.solve(state_str)
    final_sol = []
    for m in raw_sol.split():
        if '2' in m:
            base = m.replace('2', '')
            final_sol.append(base)
            final_sol.append(base)
        else:
            final_sol.append(m)
    solution = " ".join(final_sol)
    
    print(f"Found: ({len(solution.split())} moves)")
    r.recvuntil(b'>> ')
    r.sendline(solution.encode())
    print("Flag:")
    r.interactive()

if __name__ == "__main__":
    main()
```
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}** 

# Mi đi um An ti để BUG:
Nghe đề là thấy bài sẽ sử dụng nhiều yếu tố liên quan đến antidebugging, thử load vào IDA coi sao: 

![image](https://hackmd.io/_uploads/r1PSFl3G-x.png)
- Trước hết, ta cần biết rằng chương trình sẽ đăng ký 1 `Handler` handling exception nếu ở 1 đoạn nào đó được ném ra. `TLScallback` chính là nơi đăng ký Hander: ![image](https://hackmd.io/_uploads/ryE-LnvQ-x.png)

- Ta thấy rằng bài sử dụng rất nhiều Window API, đồng thời bên trong hàm main ta gặp rải rác vô cùng nhiều các lệnh `CC - INT3` và `IsDebuggerPresent` với mục đích là ngắt luồng, hook vào `IsDebuggerPresent` ghi các byte của nó để thực hiện luồng ngầm mà các Debugger như IDA không thể trace được. Nói cách khác bài làm rối hoàn toàn hàm main và ngăn cản ta debug nó.

`INT 3` sẽ gây nên Exception, mỗi khi chạy đến OPCODE này chương trình sẽ dừng và ném Exception cho `Handler` xử lý, cụ thể ta tìm được Handler trông như sau: 
![image](https://hackmd.io/_uploads/rkh-UWhGZe.png)
![image](https://hackmd.io/_uploads/HJCM8WnzWl.png)
Trong đó có: 
```cpp!
void sub_4812F0()
{
  HANDLE CurrentProcess; // eax
  DWORD flOldProtect; // [esp+0h] [ebp-8h] BYREF

  if ( !dword_484058 )
  {
    VirtualProtect(IsDebuggerPresent, 5u, 0x40u, &flOldProtect);
    IsDebuggerPresent = (BOOL (__stdcall *)())dword_484480;
    *((_BYTE *)&IsDebuggerPresent + 4) = byte_484484;
    VirtualProtect(IsDebuggerPresent, 5u, flOldProtect, &flOldProtect);// Thay 5 byte đầu tiên của IsDebuggerPresent thành 0x40 - (PAGE_EXECUTE_READWRITE
    CurrentProcess = GetCurrentProcess();
    FlushInstructionCache(CurrentProcess, IsDebuggerPresent, 5u);// Xóa Cache CPU
  }
}
``` 
```cpp!
BOOL __thiscall De_tem(_BYTE *dest)
{
  HANDLE CurrentProcess; // eax
  DWORD flOldProtect; // [esp+8h] [ebp-10h] BYREF
  _BYTE v5[5]; // [esp+Ch] [ebp-Ch]

  v5[0] = 0xE9;                                 // JMP rel32 OPCODE
  if ( dword_484058 )
  {
    dword_484480 = (int)IsDebuggerPresent;
    byte_484484 = *((_BYTE *)&IsDebuggerPresent + 4);// Lưu 5 byte của IsDebuggerPresent để sau khôi phục
    dword_484058 = 0;
  }
  VirtualProtect(IsDebuggerPresent, 5u, 0x40u, &flOldProtect);// Writable cho 5 byte đầu này
  *(_DWORD *)&v5[1] = dest - (_BYTE *)IsDebuggerPresent - 5;
  IsDebuggerPresent = *(BOOL (__stdcall **)())v5;// Đè tem IsDebuggerPresent, lúc này call  esi trong main ko gọi kiểm tra debug nữa 
                                                // Thay vào đó, sẽ bị lùa thực hiện 1 luồng nào đó khác lấy từ Proc_chart
  *((_BYTE *)&IsDebuggerPresent + 4) = v5[4];
  VirtualProtect(IsDebuggerPresent, 5u, flOldProtect, &flOldProtect);// Khôi phục quyền, xóa Cache...
  CurrentProcess = GetCurrentProcess();
  return FlushInstructionCache(CurrentProcess, IsDebuggerPresent, 5u);
}
```
## Hook Win API:
Đoạn này mìn nói một chút về hàm `De_tem`: 
- Đầu tiên ta cần hiểu về `E9 - JMP rel32` hay "Jump tương đối 32bit", nó sẽ nhảy đến địa chỉ gần nó theo công thức: 

$$
DEST(EIPsauJUMP) + Offset
$$

$$
Offset = Dest - (NextIntruction)
$$
- Trong bài, ta thấy `v5` sẽ chứa 5 byte đè lên API `IsDebuggerPresent`, với byte đầu tiên là lệnh `JMP` này. 
- 4 byte còn lại được tính toán là: `*(_DWORD *)&v5[1] = dest - (_BYTE *)IsDebuggerPresent - 5;` hay: 
$$
Offset = Dest - (IsDebuggerPresent + 5)
$$
> NextInstruction phải là DebuggerAPI + 5 do khi EIP mặc định đọc JMP rel32 0xE9 sẽ đọc thêm 4 byte nữa.
- Vậy De_tem sẽ thay thế hoàn toàn việc check Debugger thành các HÀM/API được để ở Proc_chart.

## Tiếp:
Okay, hiệu được các chương trình dấu luồng rồi thì ta thử mở Proc_chart ra xem nó đang lưu cái gì nhé: 
```
.rdata:004831B4 Proc_chart      dd offset GetStdHandle  ; DATA XREF: Handler+24↑r
.rdata:004831B4                                         ; sub_4813F0+24↑r ...
.rdata:004831B8                 dd offset GetStdHandle
.rdata:004831BC                 dd offset WriteConsoleA
.rdata:004831C0                 dd offset WriteConsoleA
.rdata:004831C4                 dd offset ReadConsoleA
.rdata:004831C8                 dd offset WriteConsoleA
.rdata:004831CC                 dd offset InitRC4
.rdata:004831D0                 dd offset RC4_ENC
.rdata:004831D4                 dd offset XTEA_ENC
.rdata:004831D8                 dd offset InitRC4
.rdata:004831DC                 dd offset RC4_ENC
.rdata:004831E0                 dd offset WriteConsoleA
.rdata:004831E4                 dd offset ExitProcess
.rdata:004831E8                 dd offset WriteConsol

// Mình đã đổi tên API/Hàm cho dễ nhìn rùi. 
```
Đã hiểu, vậy đề bài sẽ lần lượt thực hiện toàn bộ Proc_chart trên ngầm trong hàm main bằng cách:
```
-> Đi đến INT 3 
-> Exception 
-> Handler handling Exception: Sửa 5bytes đầu IsDebuggerPresent 
-> Gọi check Debugger 
-> Chạy đến luồng khác (do API này bị biến đổi) 
-> Khôi phục IsDebuggerPresent (trả 5 byte đầu bị sửa thành JUMP
-> Successfully Hiding Flow...!
```

và làm như sau:
![image](https://hackmd.io/_uploads/ByLVuW3Gbl.png)
![image](https://hackmd.io/_uploads/SJdI_Z2MZg.png)
![image](https://hackmd.io/_uploads/H1swOb3Gbg.png)
Vậy, Bản chất của `main` là đẩy các tham số lên stack cho API\Hàm trong Proc_chart chạy, và nõ chạy lần lượt từ trên xuống. Luồng gốc của đề bài thật ra chỉ như này: 

```
Nhập Input
-> Chương trình lấy Input
-> Mã hóa RC4 liked 
-> Mã hóa XTEA liked 
-> Mã hóa RC4 liked thêm lần nữa 
-> So sánh với Target
```
Và chỉ có thể thôi, ta dump các giá trị như `Keys, Target` rồi tuân theo đúng logic mã hóa truy ngược Flag từ `Target là xong bài`:
```python!
import struct
from binascii import unhexlify, hexlify

TARGET_CIPHER = unhexlify("7B6FB4F7813DEFEEB21E7E8D0C9D6741CB2053FD4771ADC6BC0EF2A26904A3B25ED7D06E636F729E")
RC4_KEY_OLD = unhexlify("1A891492225D4F")   # Key1
RC4_KEY_NEW = unhexlify("36BDD13B9AA8EF29FF") # Key2

XTEA_KEY_WORDS = [0xDEADBEEF, 0xCAFEBABE, 0x13371337, 0xBAADF00D] 
MASK = 0xFFFFFFFF 


def rc4_init(key: bytes) -> bytearray:
    state = bytearray(264) # S-box (256) + i (4) + j (4)
    key_len = len(key)
    j_val = 0
    
    # 1. Khởi tạo S-box: S[i] = i
    for i in range(256):
        state[i] = i

    # 2. Permutation
    for i in range(256):
        v6 = state[i]
        # j = (j + Key[i % key_len] + S[i]) & 0xFF
        j_val = (j_val + key[i % key_len] + v6) & 0xFF
        
        # swap S[i], S[j]
        tmp = state[j_val]
        state[i] = tmp
        state[j_val] = v6

    state[256:264] = b'\x00' * 8
    
    return state

def xtea_decrypt_block(v0, v1, key):
    delta = 0x61C88647
    mask = 0xFFFFFFFF
    sum_ = (-delta * 32) & mask
    for _ in range(32):
        v1 = (v1 - ((sum_ + v0) ^ (key[2] + ((v0 << 4) & mask)) ^ (key[3] + (v0 >> 5)))) & mask
        v0 = (v0 - ((sum_ + v1) ^ (key[0] + ((v1 << 4) & mask)) ^ (key[1] + (v1 >> 5)))) & mask
        sum_ = (sum_ + delta) & mask

    return v0, v1
    
def rc4_decrypt(state: bytearray, buf: bytearray, offset: int, length: int):
    if length <= 0:
        return
    i = int.from_bytes(state[256:260], "little")
    j = int.from_bytes(state[260:264], "little")
    for n in range(length):
        i = (i + 1) & 0xFF
        j = (j + state[i]) & 0xFF
        tmp = state[i]
        state[i] = state[j]
        state[j] = tmp
        k = state[(state[i] + state[j]) & 0xFF]
        buf[offset + n] ^= k
        
    state[256:260] = i.to_bytes(4, "little")
    state[260:264] = j.to_bytes(4, "little")
    
def xtea_decrypt(data: bytes, key):
    out = bytearray()
    for i in range(0, len(data), 8):
        v0 = int.from_bytes(data[i:i+4], "little")
        v1 = int.from_bytes(data[i+4:i+8], "little")

        v0, v1 = xtea_decrypt_block(v0, v1, key)

        out += v0.to_bytes(4, "little")
        out += v1.to_bytes(4, "little")
    pad = out[-1]
    return bytes(out[:-pad])

def main():
    rc4_state_new = rc4_init(RC4_KEY_NEW)
    ciphertext_buffer = bytearray(TARGET_CIPHER)
    # REV RC4-2
    rc4_decrypt(rc4_state_new, ciphertext_buffer, 0, len(ciphertext_buffer))
    XTEA_INPUT_CIPHER = bytes(ciphertext_buffer)
    # XTEA
    RC4_INPUT_TEMP = xtea_decrypt(XTEA_INPUT_CIPHER, XTEA_KEY_WORDS)
    
    # REV RC4-1
    rc4_state_old = rc4_init(RC4_KEY_OLD)
    flag_buffer = bytearray(RC4_INPUT_TEMP)
    rc4_decrypt(rc4_state_old, flag_buffer, 0, len(flag_buffer))
    
    FINAL_FLAG = bytes(flag_buffer)
    try:
        print(f"FLAG: {FINAL_FLAG.decode('latin-1')}")
    except Exception:
        print(f"FLAG HEX{hexlify(FINAL_FLAG).decode()}")
if __name__ == "__main__":
    main()
```
Flag: **KCSC{now_you_know_how_to_hook_WINAPI}** 
## Cách giải thứ 2: Tracing trực tiếp để luận logic: 
Cách trên mình nhìn vào `Proc_chart` và code tại hàm `main` để đoán luồng chương trình. Khi đã hiểu rõ cách Handler bài này hoạt động, ta có thể tracing trực tiếp để luận luồng. 

Nói sơ qua lại 1 chút hàm `Handler` custom được sử dụng, nó ghi đè byte của WindowAPI `IsDebuggerPresent` để khi API này được gọi bản chất luồng thật sẽ được chạy: 
```cpp
LONG __stdcall Handler(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
  PCONTEXT ContextRecord; // esi
  PVOID v2; // eax

  if ( ExceptionInfo->ExceptionRecord->ExceptionCode != 0x80000003 )
    return 0;
  ContextRecord = ExceptionInfo->ContextRecord;
  Remove_old_Isdebuger_hook();
  if ( dword_7F43E4 < 15 )
  {
    De_tem(Proc_chart[dword_7F43E4]);
    ++dword_7F43E4;
  }
  if ( Handle )
    RemoveVectoredExceptionHandler(Handle);
  v2 = AddVectoredExceptionHandler(1u, sub_7F13F0);// Đăng ký Handler mới, đảm bảo nó xuất hiện đầu tiên để xử lý Exception mới được ném ra, các địa chi Handler mới sẽ khác với mỗi lần đăng ký
  ++ContextRecord->Eip;                         // ContextRecord chính là trạng thái của CPU khi dừng lại tại lệnh INT3, tăng nó lên 1 sau khi thao túng API để tiếp tục tới lệnh tiếp theo
  Handle = v2;
  return -1;
}
``` 
- Ta thấy rằng, chương trình đăng ký 1 Handler mới sau khi Handler hiện tại xử lý xong Exception ném bởi INT3 và địa chỉ của Handler mới này sẽ khác nhau với mỗi lần đăng ký. Nếu đã đọc hiểu code của `TLScallback` thì đoạn đăng ký Handler mới gần như giống hệt: 
>Đây là code bên TLScallback:
![image](https://hackmd.io/_uploads/B10ULhvQbg.png)
>Đây là code bên Handler: 
![image](https://hackmd.io/_uploads/ryyu8hvX-g.png)

- Vậy để trace thì đơn giản ta sẽ đặt breakpoint tại mỗi vị trí bắt đầu của các Handler rồi soi giá trị luồng được lấy từ Proc_chart là gì hoặc Stepin vào `call IsDebuggerPresent`(cụ thể là dòng này):
![image](https://hackmd.io/_uploads/HkZBMTLm-x.png)
![image](https://hackmd.io/_uploads/ryd9z6L7be.png)
>Ví dụ: Ở ảnh dưới là mình cho StepIn call IsDebuggerPresent hiện tại là GetStdHanle hay chính là lần call API đầu tiên, ta có thể từ đó đoán được luồng tiếp theo...
- Cứ làm tương tự như vậy - F7 soi API rồi dần dần ta sẽ nhìn thấy flow của chương trình giống hệt cách mình đã phân tích tĩnh ở trên. 

### Phân tích: 
Mình sẽ đặt breakpoint ở mỗi lần gọi `IBP - IsDebuggerPresent` rồi Stepin cho trực quan. 

Lần gọi `IBP` đầu tiên, ta thấy rằng IBP đã bị patch thành: 
![image](https://hackmd.io/_uploads/H1KY8l3QWx.png)
- Tham số cho hàm này chỉ có 1 và được đẩy lên stack là `-11 - STD_OUTPUT_HANDLE`:
![image](https://hackmd.io/_uploads/SyatwgnXbe.png)

`IBP` sau đó tiếp tục được patch thành `GetStvHandle`: 
![image](https://hackmd.io/_uploads/H1FQ_e3Qbl.png)
- Tham số được truyền là `-10 - STD_INPUT_HANDLE`: ![image](https://hackmd.io/_uploads/S1BD_ln7-g.png)

Tiếp tục, `IBP` lần này được patch thành `WriteConsoleA`, 1 Windows API đẩy text ra Output (Hiện đăng ký là Màn hình): 
![image](https://hackmd.io/_uploads/Hysa_lhmZl.png)
- Tham số được push lên stack lần lượt là: ![image](https://hackmd.io/_uploads/H1AeKl2X-e.png)
- Ta có thể đọc nhanh cách sử dụng WriteConsoleA [tại đây](https://learn.microsoft.com/en-us/windows/console/writeconsole)

`IBP` sau đó tiếp tục bị patch thành `WriteConsoleA`, đoạn này là để in `Enter:`: ![image](https://hackmd.io/_uploads/SJOZqx2mWe.png)

Lần tiếp theo thì `IBP` sẽ bị ghi đè bởi `ReadConsoleA`: ![image](https://hackmd.io/_uploads/r1VhNznQ-e.png)
- Đây là tham số được đưa vào API này, ta có thể đọc về API này [tại đây](https://learn.microsoft.com/en-us/windows/console/readconsole): ![Screenshot 2025-12-26 210050](https://hackmd.io/_uploads/Hy7vrGhXWx.png)
- Sau khi chạy xong thì Flag được lưu ở `FlagBuf` và Lenght được lưu tại `ptrFlaglen` chuẩn như đã dự đoán: 
![image](https://hackmd.io/_uploads/HJv1_fhmbe.png)
![image](https://hackmd.io/_uploads/HyhCvz37Zx.png)

Sau khi đọc xong thì `FlagLenght` được lưu vào eax và đề bài bắt buộc nó phải dài khoảng `64byte`, mình đặt RIP sang luồng bên trái do đang trace để hiểu code, đến đoạn sau ta thấy nó có cấu trúc tương tự như khi đẩy `KCSC...` HAY `Enter...` với input là `STD_OUTPUT_HANDLE` nên ta đoán ngay lần tới cũng chỉ in text lên màn hình ![image](https://hackmd.io/_uploads/HJF3HMnQbx.png)

Trace tiếp thì các đoạn call API trá hình sau nằm đúng như những gì mình đoán khi nó sẽ mã hóa Input của ta theo những gì đã phân tích khi lần lượt gọi các hàm như `RC4 -> XTEA -> RC4` để mã hóa Input của ta. Đây chính là cách để xác minh những gì mình đoán ở cách 1. Đây là cũng là một cách luận luồng chương trình thay vì đoán thứ tự dựa vào `Proc_chart` để có thể hiểu chính xác hơn luồng của code giả sử nếu bài khó hơn khi mà thứ tự `Proc_chart` bị shuffle...
# Just_EzReversing (easy): 
IDA GO GO...
```cpp!
__int64 __fastcall sub_140009BD0(char *a1, const char *a2)
{
  int v2; // r8d
  int v3; // r9d
  size_t v4; // rax
  int v5; // edx
  int v6; // r8d
  int v7; // r9d
  size_t v8; // rbx
  const char *b32_input; // rsi
  size_t v10; // rdx
  int v11; // eax
  int v12; // edx
  int v13; // r8d
  int v14; // r9d

  sub_1400026C7();
  sub_140009B80((_DWORD)a1, (_DWORD)a2, (unsigned int)aEnterTheFlag, (unsigned int)"%s", v2, v3);// scanning
  __acrt_iob_func((unsigned int)a1);
  fgets(a1, (int)a2, (FILE *)0x64);
  input[strcspn(a1, a2)] = 0;
  v4 = strlen(a1);
  if ( (double)(int)v4 == 36.0 )                // lenght = 36
  {
    v8 = 0LL;
    b32_input = (const char *)base32_enc(a1, a2, v4, input, 1LL, (double)(int)v4);
    while ( v8 < strlen(Buf1) )
    {
      v10 = (size_t)&b32_input[v8];             // v10 lần lượt sẽ là: b32_input[v8:v8+2]...
      v8 += 2LL;
      strncpy(Buf1, b32_input, v10);
      MD5(Buf1, b32_input, Buf1, &unk_140010324);
      v11 = dword_1400100A0;
      *(__m128i *)((char *)&store + dword_1400100A0) = _mm_load_si128((const __m128i *)Buf1);
      dword_1400100A0 = v11 + 16;
    } //Lần lượt MD các Block b32_inut
    if ( !memcmp(Buf1, b32_input, (size_t)&Size) ) 
    {
      sub_140009B80((unsigned int)Buf1, (_DWORD)b32_input, v12, (unsigned int)"Correct!\n", v13, v14);
      return 1LL;
    }
    else
    {
      sub_140009B80((unsigned int)Buf1, (_DWORD)b32_input, v12, (unsigned int)"Incorrect!\n", v13, v14);
      return 0LL;
    }
  }
```
Tại sao mình biết được đề dùng 2 hàm (Base32_enc và MD5), thử debug động ta thấy sau khi chạy b32_enc, b32_input có dạng: 
![image](https://hackmd.io/_uploads/B1tZ8W6fWx.png)
- Sau khi chạy `MD5 thì kết quả được lưu ở Buf1 là MD5 2 ký tự đầu` rồi đẩy vào `store`: 
![image](https://hackmd.io/_uploads/SkjCL-TzWg.png)
![image](https://hackmd.io/_uploads/HyGSw-TG-g.png)

- Lần lặp thứ 2, ta thu được lần lượt là b32 của 2 ký tự sau đó và MD5: 
 ![image](https://hackmd.io/_uploads/H18PDbTGZx.png)


Bài này khá đơn giản, ta chỉ cần tìm Input sao cho: 
`MD5( b32_input[2*i : 2*i+2] ) == Size[16*i : 16*i+16]` là ok!! Ta chỉ cần dump cái mảng `Size` ra là được: 

```python!
import hashlib
import itertools
import base64
BASE32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
SIZE = bytes.fromhex(
    "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 "
    "BF D2 34 39 10 37 8A 1B 53 7C 3C 80 36 A5 C0 7E "
    "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"
)

def solve():
    blocks = [SIZE[i:i+16] for i in range(0, len(SIZE), 16)]
    result = ""

    for idx, target in enumerate(blocks):
        found = False
        for a, b in itertools.product(BASE32, repeat=2):
            h = hashlib.md5((a+b).encode()).digest()
            if h == target:
                result += a + b
                print(f"Block {idx}: {a+b}")
                found = True
                break
        if not found:
            raise RuntimeError("het")

    return result

b32 = solve()
raw = base64.b32decode(b32)
print(raw)
```

Ta được 1 chuỗi Base32 như này: `JNBVGQ33GN5F6YTSOU3TGX3NMQ2V6NDOMRPWIM3DN5SDGX3CGRZTGMZSPU`. 

Mang đi decode để được Flag: 
Flag: **KCSC{3z_bru73_md5_4nd_d3cod3_b4s332}** 

# The reward lies hidden (easy): 
Nếu để nhận xét thì chắc câu này khoai nhất trong các câu EZ :v
Nạp file vào IDA, tìm nhanh main: 
```cpp!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rbx
  unsigned __int64 v4; // r14
  __int128 v5; // rdi
  size_t v6; // rax
  void *v7; // rax
  _QWORD *v8; // r14
  size_t v9; // rcx
  void *v10; // rcx
  void **v11; // rdx
  __int64 v12; // rax
  __int64 v13; // rdx
  unsigned __int64 v14; // r8
  void **v15; // r9
  void **v16; // rax
  __int64 v17; // rcx
  void **v18; // rax
  __int64 v19; // rcx
  void **v20; // rax
  __int64 v21; // rcx
  void **v22; // rax
  __int64 v23; // rcx
  void **v24; // rax
  __int64 v25; // rcx
  void **v26; // rax
  __int64 v27; // rcx
  void **v28; // rax
  __int64 v29; // rcx
  void **v30; // rax
  __int64 i; // rsi
  __int64 v32; // rax
  __int64 v33; // rax
  __int64 j; // rdx
  __int64 still_the_result; // rax
  unsigned __int64 v36; // rcx
  char *v37; // rdi
  void *v38; // rcx
  __int128 v40; // [rsp+20h] [rbp-E0h] BYREF
  __m128i v41; // [rsp+30h] [rbp-D0h]
  __int64 result[24]; // [rsp+50h] [rbp-B0h]
  void *Block[2]; // [rsp+110h] [rbp+10h] BYREF
  __m128i v44; // [rsp+120h] [rbp+20h]

  v3 = 0i64;
  Src[0] ^= 0x12u;
  byte_7FF6D291D0B5 ^= 0x3Du;
  byte_7FF6D291D0B6 ^= 0x46u;
  byte_7FF6D291D0B7 ^= 0xA1u;
  byte_7FF6D291D0B8 ^= 0x42u;
  byte_7FF6D291D0B9 ^= 0xDFu;                   // sau khi XOR được lần lượt Flag:
  *(_OWORD *)Block = 0i64;
  v44.m128i_i64[0] = 0i64;
  v4 = 15i64;
  v44.m128i_i64[1] = 15i64;
  LOBYTE(Block[0]) = 0;
  v40 = 0i64;
  *(_QWORD *)&v5 = -1i64;
  do
    *(_QWORD *)&v5 = v5 + 1;
  while ( Src[v5] );
  *((_QWORD *)&v5 + 1) = 0x7FFFFFFFFFFFFFFFi64;
  if ( (unsigned __int64)v5 > 0x7FFFFFFFFFFFFFFFi64 )
    ((void (__fastcall __noreturn *)(int, const char **, const char **))sub_7FF6D2911220)(argc, argv, envp);
  if ( (unsigned __int64)v5 <= 0xF )
  {
    v41.m128i_i64[0] = v5;
    v41.m128i_i64[1] = 15i64;
    memcpy(&v40, Src, v5);
    *((_BYTE *)&v40 + v5) = 0;
    goto LABEL_19;
  }
  if ( ((unsigned __int64)v5 | 0xF) > 0x7FFFFFFFFFFFFFFFi64 )
  {
    v6 = 0x8000000000000027ui64;
LABEL_8:
    v7 = operator new(v6);
    if ( !v7 )
      goto LABEL_22;
    v8 = (_QWORD *)(((unsigned __int64)v7 + 39) & 0xFFFFFFFFFFFFFFE0ui64);
    *(v8 - 1) = v7;
    goto LABEL_18;
  }
  *((_QWORD *)&v5 + 1) = v5 | 0xF;
  if ( ((unsigned __int64)v5 | 0xF) < 0x16 )
    *((_QWORD *)&v5 + 1) = 22i64;
  v9 = *((_QWORD *)&v5 + 1) + 1i64;
  if ( *((_QWORD *)&v5 + 1) == -1i64 )
  {
    v8 = 0i64;
  }
  else
  {
    if ( v9 >= 0x1000 )
    {
      v6 = *((_QWORD *)&v5 + 1) + 40i64;
      if ( *((_QWORD *)&v5 + 1) + 40i64 <= (unsigned __int64)(*((_QWORD *)&v5 + 1) + 1i64) )
        sub_7FF6D2911180(v9, argv, envp);
      goto LABEL_8;
    }
    v8 = operator new(v9);
  }
LABEL_18:
  *(_QWORD *)&v40 = v8;
  v41 = (__m128i)v5;
  memcpy(v8, Src, v5);
  *((_BYTE *)v8 + v5) = 0;
  v4 = v44.m128i_u64[1];
LABEL_19:
  if ( v4 > 0xF )
  {
    v10 = Block[0];
    if ( v4 + 1 >= 0x1000 )
    {
      v10 = (void *)*((_QWORD *)Block[0] - 1);
      if ( (unsigned __int64)(Block[0] - v10 - 8) > 0x1F )
LABEL_22:
        invalid_parameter_noinfo_noreturn();
    }
    j_j_free(v10);
  }
  *(_OWORD *)Block = v40;
  v44 = v41;
  v11 = Block;
  if ( _mm_srli_si128(v41, 8).m128i_u64[0] > 0xF )
    v11 = (void **)v40;
  v12 = sub_7FF6D2912880(std::cout, (__int64)v11, v41.m128i_i64[0]);
  std::ostream::operator<<(v12, sub_7FF6D2912260);
  getLine(std::cin, Block);                     // Block chứa String input 
  if ( (v44.m128i_i64[0] ^ 0x7A) == 194 )       // inputlenght check
  {                                             // Đóng gói theo dạng: 
                                                // [C6,C3,C1,C0,C4,C2,C7]
    v13 = 0i64;
    v14 = v44.m128i_u64[1];
    v15 = (void **)Block[0];
    do
    {
      v16 = Block;
      if ( v14 > 0xF )
        v16 = v15;
      v17 = SBYTE6(v16[v13]);
      v18 = Block;
      if ( v14 > 0xF )
        v18 = v15;
      v19 = ((__int64)SBYTE3(v18[v13]) << 8) | v17;
      v20 = Block;
      if ( v14 > 0xF )
        v20 = v15;
      v21 = ((__int64)SBYTE1(v20[v13]) << 16) | v19;
      v22 = Block;
      if ( v14 > 0xF )
        v22 = v15;
      v23 = ((__int64)SLOBYTE(v22[v13]) << 24) | v21;
      v24 = Block;
      if ( v14 > 0xF )
        v24 = v15;
      v25 = ((__int64)SBYTE4(v24[v13]) << 32) | v23;
      v26 = Block;
      if ( v14 > 0xF )
        v26 = v15;
      v27 = ((__int64)SBYTE5(v26[v13]) << 40) | v25;
      v28 = Block;
      if ( v14 > 0xF )
        v28 = v15;
      v29 = ((__int64)SBYTE2(v28[v13]) << 48) | v27;
      v30 = Block;
      if ( v14 > 0xF )
        v30 = v15;
      result[v13] = v29 | ((__int64)SHIBYTE(v30[v13]) << 56);
      ++v13;
    }
    while ( v13 < 23 );
    for ( i = 0i64; i < 23; ++i )
    {
      v32 = int64_mul(*(_QWORD *)&A[i * 8 + 64], *(_QWORD *)((char *)&B + i * 8));
      v33 = int64_mul(v32, *(_QWORD *)&A[i * 8 + 512]);
      result[i] = int64_mul(v33, result[i]);
    }
    for ( j = 0i64; j < 184; j += 8i64 )
    {
      still_the_result = result[(unsigned __int64)j / 8];
      v36 = (((((((still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result
                                                                                  - *(_QWORD *)&A[j + 256]) >> 32)) >> 16) | (still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result - *(_QWORD *)&A[j + 256]) >> 32)) >> 8) | (((still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result - *(_QWORD *)&A[j + 256]) >> 32)) >> 16) | (still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result - *(_QWORD *)&A[j + 256]) >> 32)) >> 4) | (((((still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result - *(_QWORD *)&A[j + 256]) >> 32)) >> 16) | (still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result - *(_QWORD *)&A[j + 256]) >> 32)) >> 8) | (((still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result - *(_QWORD *)&A[j + 256]) >> 32)) >> 16) | (still_the_result - *(_QWORD *)&A[j + 256]) | ((unsigned __int64)(still_the_result - *(_QWORD *)&A[j + 256]) >> 32);
      if ( (((unsigned int)(v36 >> 2) | (unsigned int)v36 | (unsigned int)(((v36 >> 2) | v36) >> 1)) & 1) != 0 )// LSB V36 == 0 OR 1 (kết quả đoạn so sánh trên)
      {
        sub_7FF6D2911020("\n");
        do
        {
          sub_7FF6D29126B0((__int64 *)std::cout, A[v3 + 488] ^ A[v3 + 40]);
          ++v3;
        }
        while ( v3 < 14 );
        goto LABEL_59;
      }
    }
    sub_7FF6D2911020("\n");
    v37 = (char *)&unk_7FF6D29146F0;
    do
    {
      sub_7FF6D2911020("%c");
      if ( (unsigned int)v3 % 0x61 == 96 )
        sub_7FF6D2911020("\n");
      LODWORD(v3) = v3 + 1;
      v37 += 4;
    }
    while ( (int)v3 < 6402 );
  }
  else
  {
    do
    {
      sub_7FF6D29126B0((__int64 *)std::cout, A[v3 + 440] ^ A[v3]);
      ++v3;
    }
    while ( v3 < 34 );
  }
LABEL_59:
  if ( v44.m128i_i64[1] > 0xFui64 )
  {
    v38 = Block[0];
    if ( (unsigned __int64)(v44.m128i_i64[1] + 1) >= 0x1000 )
    {
      v38 = (void *)*((_QWORD *)Block[0] - 1);
      if ( (unsigned __int64)(Block[0] - v38 - 8) > 0x1F )
        invalid_parameter_noinfo_noreturn();
    }
    j_j_free(v38);
  }
  return 0;
}
```
Trông khá rối nhỉ, nhưng bản chất, main thực hiện đúng 3 thứ duy nhất: 
- Đầu tiên, nó nhận Input bắt buộc phải dài 184 byte. 
```cpp!
 getLine(std::cin, Block);                     // Block chứa String input 
  if ( (v44.m128i_i64[0] ^ 0x7A) == 194 )       // inputlenght check
  {                                             // Đóng gói theo dạng: 
                                                // [C6,C3,C1,C0,C4,C2,C7]
    v13 = 0i64;
    v14 = v44.m128i_u64[1];                     // v14 cầm độ lớn Block (>15 thì lưu trên Heap)
    v15 = (void **)Block[0];                    // Block ptr
    do
    {
      v16 = Block;
      if ( v14 > 0xF )
        v16 = v15;
      v17 = SBYTE6(v16[v13]);                   // Lấy C6
      v18 = Block;
      if ( v14 > 0xF )
        v18 = v15;
      v19 = ((__int64)SBYTE3(v18[v13]) << 8) | v17;// Lấy C3, ghép sau C6 
      v20 = Block;
      if ( v14 > 0xF )
        v20 = v15;
      v21 = ((__int64)SBYTE1(v20[v13]) << 16) | v19;// ... Logic tương tự
      v22 = Block;
      if ( v14 > 0xF )
        v22 = v15;
      v23 = ((__int64)SLOBYTE(v22[v13]) << 24) | v21;
      v24 = Block;
      if ( v14 > 0xF )
        v24 = v15;
      v25 = ((__int64)SBYTE4(v24[v13]) << 32) | v23;
      v26 = Block;
      if ( v14 > 0xF )
        v26 = v15;
      v27 = ((__int64)SBYTE5(v26[v13]) << 40) | v25;
      v28 = Block;
      if ( v14 > 0xF )
        v28 = v15;
      v29 = ((__int64)SBYTE2(v28[v13]) << 48) | v27;
      v30 = Block;
      if ( v14 > 0xF )
        v30 = v15;
      result[v13] = v29 | ((__int64)SHIBYTE(v30[v13]) << 56);// result[i] = _int64()[C6,C3,...C7] <- C7 đưa vào cuối
      ++v13;
    }
```
- Tiếp theo, với mỗi 8 byte của Input, nó thực hiện `Shuffle Packin`, chuyển thành 1 cục QWORD như này: `[C6,C3,C1,C0,C4,C2,C7] = result[]` rồi lưu nó thành 1 array kiểu: `Output[23Qword] = [result[0],result[1]] . 
    - Debugging động, sau lần lặp đầu tiên, result trông có dạng: 
![image](https://hackmd.io/_uploads/ryDRtZ6MZe.png)
    - Input mình nhập là `KCSC{aaaaaa...a}`, có thể thấy việc đóng gói mà mình phát hiện ra là đúng. 
- Cuối cùng, nó cầm cái `Output` này đi nhân với `A*B*C` theo `imul_int64`, rồi đem so sánh nó với `Target`. 

Oh wow, vậy chẳng phải ta chỉ cần thực hiện chia ngược lại `Target` cho `A*B*C` là ra Shuffle Input rồi và chỉ cân đảo byte là ra được Flag hay sao :v? 

Hiểu được điều này, giờ mình chỉ cần: 
- Dump A và B từ IDA (2 Blob này chứa các Số hạng lẫn `Target` lun, tùy cách dùng mà lấy các Block khác nhau): 
```python
 ConstA = [
    0x21F63DF6EA176765, 0x0AB1FD5C1E0A9765, 0x40382E78D2BF618F, 0x1883A77DECE9FAF7,
    0x7EF4324F07E21EB5, 0x8C27B1CABDC5778F, 0x4AB7BC73427DF467, 0x0FEE7AA89FBDCF35F,
    0x0FBFB328BB71B46B1, 0x3A54E11FDDC74D27, 0x0AABADEE9278DCE1D, 0x64A10164A8BDBE83,
    0x0D64ADFD1EF8882C1, 0x0A3CDC5274D7B6CD5, 0x05388BCE06632DA1, 0x961201B184768A51,
    0x0F318EB353D285DF7, 0x0F62D256FF95FAD05, 0x0C8D42BE55BD754DB, 0x0D1C86DAB8378475F,
    0x8537555ABD8B7219, 0x0EC927CC094A2DAFD, 0x0D278D636CCBE1A01
]

ConstB = [
    0xB695C2BD10A19523, 0x3B858B3E8671B94D, 0x9D99CDB818E8BF0F, 0xEC2AFD8FC9140303,
    0xC3E16C0BA35B97B9, 0x5085BA414E33D2DD, 0xF001B18435C1CC93, 0xBD9A607B178C4267,
    0xF848AEC0E54EA671, 0xEC40D1CE64B42A89, 0x7A69DCA7959C9975, 0xE7015D92ECA6C995,
    0x9C70C9956AD4CC6D, 0x4B95EE2F5CB11853, 0x2A1FD7BE9190BD25, 0xF425838CD21568FB,
    0x6319F7F42D581163, 0xC9DA1F9E8F790A0F, 0x1076477A4D86F435, 0xFD7C305A832AD541,
    0x2D2ED34F5A8ED569, 0x820C45DC6BD2EB43, 0x0A91349CBCB508CC5
]

ConstC = [
    0x0BC7C3C0B0BE5E531, 0x111EC8CE746A6243, 0x499851BCDA2073BD, 0x36E0DD8C4C897C55,
    0x1DEA534DA4FCFEB7, 0x7B57E4E5165E26B9, 0x4A1C2B6AC91891A7, 0x44EEC6699738FA8D,
    0x0AD4DFE6B00A02157, 0x2EB554CA9635FAFB, 0x623F7B0197F79C13, 0x0A3415911EFF3BED9,
    0x4B4FD9B5E21BCFF3, 0x6F78C98DC732D231, 0x2AEC3662719E09CB, 0x915462C402BE8401,
    0x0FA2BC3A4BBFA039, 0x669B5347E3C2CAE5, 0x13ED2473AC48603D, 0x0F5DDA0418D09D53B,
    0x2548ACAB280A4AE3, 0x858E612083B3E24B, 0x1FDF3D9116AC2313
]

Target = [
    0x47EE0B7C8B7D3CBB, 0x0E6287DF7349CED, 0x777BEDA8C9D49B43, 0x56F80210D3708C41,
    0x0CA97B6C052AEF171, 0x78B8C3301E2A2DF8, 0x0B3B52628FA5B7DFD, 0x0A8CBBC861ECE9708,
    0x8674B6F2DE5669EC, 0x20B5D04D1270C20E, 0x201451D60FC4D54D, 0x0CAFCB8DF62EB5239,
    0x8266259ED31390A1, 0x7A6B60936C3EE9B1, 0x82F95AE8EBFDF80F, 0x0E992454B2A028365,
    0x0CCA3EB3AEBAAAAF1, 0x4F7B7482BA6B9B50, 0x32A448B338174DBC, 0x9BF0D87A84A81B7A,
    0x90B0BCC994E1A8C3, 0x907F8298C8E385FC, 0x9C6EC09F1FA0F07F
]
```
- Tính theo Logic ngược lại:

$$\text{Input}_\text{gốc} \xrightarrow{\text{Shuffle/Pack}} \text{Input}_\text{Khối} \xrightarrow{\times K \pmod{2^{64}}} \text{Target}$$

- Giờ chuyển thành: 

$$\text{Input}_\text{Khối} = \text{Target} \times K^{-1} \pmod{2^{64}} \xrightarrow{\text{Unshuffle}} \text{Input}_\text{gốc}$$

```python!
from Crypto.Util.number import inverse

ConstA = [
    0x21F63DF6EA176765, 0x0AB1FD5C1E0A9765, 0x40382E78D2BF618F, 0x1883A77DECE9FAF7,
    0x7EF4324F07E21EB5, 0x8C27B1CABDC5778F, 0x4AB7BC73427DF467, 0x0FEE7AA89FBDCF35F,
    0x0FBFB328BB71B46B1, 0x3A54E11FDDC74D27, 0x0AABADEE9278DCE1D, 0x64A10164A8BDBE83,
    0x0D64ADFD1EF8882C1, 0x0A3CDC5274D7B6CD5, 0x05388BCE06632DA1, 0x961201B184768A51,
    0x0F318EB353D285DF7, 0x0F62D256FF95FAD05, 0x0C8D42BE55BD754DB, 0x0D1C86DAB8378475F,
    0x8537555ABD8B7219, 0x0EC927CC094A2DAFD, 0x0D278D636CCBE1A01
]
ConstB = [
    0xB695C2BD10A19523, 0x3B858B3E8671B94D, 0x9D99CDB818E8BF0F, 0xEC2AFD8FC9140303,
    0xC3E16C0BA35B97B9, 0x5085BA414E33D2DD, 0xF001B18435C1CC93, 0xBD9A607B178C4267,
    0xF848AEC0E54EA671, 0xEC40D1CE64B42A89, 0x7A69DCA7959C9975, 0xE7015D92ECA6C995,
    0x9C70C9956AD4CC6D, 0x4B95EE2F5CB11853, 0x2A1FD7BE9190BD25, 0xF425838CD21568FB,
    0x6319F7F42D581163, 0xC9DA1F9E8F790A0F, 0x1076477A4D86F435, 0xFD7C305A832AD541,
    0x2D2ED34F5A8ED569, 0x820C45DC6BD2EB43, 0x0A91349CBCB508CC5
]
ConstC = [
    0x0BC7C3C0B0BE5E531, 0x111EC8CE746A6243, 0x499851BCDA2073BD, 0x36E0DD8C4C897C55,
    0x1DEA534DA4FCFEB7, 0x7B57E4E5165E26B9, 0x4A1C2B6AC91891A7, 0x44EEC6699738FA8D,
    0x0AD4DFE6B00A02157, 0x2EB554CA9635FAFB, 0x623F7B0197F79C13, 0x0A3415911EFF3BED9,
    0x4B4FD9B5E21BCFF3, 0x6F78C98DC732D231, 0x2AEC3662719E09CB, 0x915462C402BE8401,
    0x0FA2BC3A4BBFA039, 0x669B5347E3C2CAE5, 0x13ED2473AC48603D, 0x0F5DDA0418D09D53B,
    0x2548ACAB280A4AE3, 0x858E612083B3E24B, 0x1FDF3D9116AC2313
]
Target = [
    0x47EE0B7C8B7D3CBB, 0x0E6287DF7349CED, 0x777BEDA8C9D49B43, 0x56F80210D3708C41,
    0x0CA97B6C052AEF171, 0x78B8C3301E2A2DF8, 0x0B3B52628FA5B7DFD, 0x0A8CBBC861ECE9708,
    0x8674B6F2DE5669EC, 0x20B5D04D1270C20E, 0x201451D60FC4D54D, 0x0CAFCB8DF62EB5239,
    0x8266259ED31390A1, 0x7A6B60936C3EE9B1, 0x82F95AE8EBFDF80F, 0x0E992454B2A028365,
    0x0CCA3EB3AEBAAAAF1, 0x4F7B7482BA6B9B50, 0x32A448B338174DBC, 0x9BF0D87A84A81B7A,
    0x90B0BCC994E1A8C3, 0x907F8298C8E385FC, 0x9C6EC09F1FA0F07F
]

def solve():
    flag_bytes = b""
    mask = 0xFFFFFFFFFFFFFFFF 

    print("[*] Decrypting and Unshuffling...")

    for i in range(23):
        K = (ConstA[i] * ConstB[i] * ConstC[i]) & mask
        K_inv = inverse(K, 2**64)
        ori_val = (Target[i] * K_inv) & mask
        jumbled = ori_val.to_bytes(8, 'little')
        
        ordered = bytearray(8)   #Unshuffling... 
        ordered[0] = jumbled[3] 
        ordered[1] = jumbled[2] 
        ordered[2] = jumbled[6] 
        ordered[3] = jumbled[1] 
        ordered[4] = jumbled[4]
        ordered[5] = jumbled[5]
        ordered[6] = jumbled[0]
        ordered[7] = jumbled[7]
        flag_bytes += ordered

    final_flag = flag_bytes
    print(f"FLAG: {final_flag}")
if __name__ == "__main__":
    solve()
```

Flag: **KCSC{Welcome_to_the_RE_category_in_this_CTF!_Thank_you_for_fighting_until_the_very_end_of_this_challenge._If_you_are_First_Blood,_you_earn_a_100k_bounty._Congrats!_Stay_sharp,_hacker!}** 

# keygenme (medium): 
Oh wow ta có bài GUI App C++ nè :D
- Mở File lên ta được như này: ![image](https://hackmd.io/_uploads/ryMgFXnGZg.png)
- Nhấn Active thì nó sẽ ra: ![image](https://hackmd.io/_uploads/SJMMKXhfZg.png)
- Hoặc: ![image](https://hackmd.io/_uploads/BJzXYm3G-l.png)
- Đem chúng đi giải mã thì biết được chúng là FakeFlag.
- 
Okay vậy là ta cần tìm username và Key đúng là xong, nạp file zô IDA bắt đầu ngắm nghía: 

Trước hết, ta tìm được `WinMain`: 
```cpp!
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  _QWORD *v6; // rax
  ATOM v7; // dx
  int result; // eax
  HWND Window; // rbx
  int v10; // edi
  int v11; // ebp
  int SystemMetrics; // esi
  int v13; // eax
  struct tagMSG Msg; // [rsp+60h] [rbp-E8h] BYREF
  struct tagRECT Rect; // [rsp+90h] [rbp-B8h] BYREF
  WNDCLASSW WndClass; // [rsp+A0h] [rbp-A8h] BYREF
  WCHAR ClassName[4]; // [rsp+F0h] [rbp-58h] BYREF
  __int64 v18; // [rsp+F8h] [rbp-50h]
  __int64 v19; // [rsp+100h] [rbp-48h]
  __int64 v20; // [rsp+108h] [rbp-40h]
  INITCOMMONCONTROLSEX picce; // [rsp+118h] [rbp-30h] BYREF

  picce.dwSize = 8;
  picce.dwICC = 0x4000;
  InitCommonControlsEx(&picce);
  v6 = VirtualAlloc(0i64, 0x110ui64, 0x3000u, 0x40u);
  Dynamic_FlagcheckFunc__ = v6;
  if ( v6 )
  {
    *v6 = bigdata[0];                           // 4885D2550F94C04D85C05756534889D30F94C208D00F85ED00000031C04885C90F84E2000000803C0100740B48FFC0483D0001000075EF31D2803C1300741448FFC24881FA0001000075EE41BB00010000EB034189D34585DB0F84A900000083F80C0F85A000000031C080396B0F8597000000807902610F858D000000807904330F858300000080790633757D80790835757780790A6E75718079016D756B80790372756580790576755F80790772755980790921755380790B39754D4531D241B937130000BE0C0000004489D0420FBE2C1399F7FE4489C84431CD4863D20FB63C1131D2F7F631EF89D2440FB60C114101F947390C90750F49FFC24539D37FCAB801000000EB02
    v6[33] = Tail_of_bigdata;                   // 31C05B5E5F5DC390
    qmemcpy(
      (void *)((unsigned __int64)(v6 + 1) & 0xFFFFFFFFFFFFFFF8ui64),
      (const void *)((char *)bigdata - ((char *)v6 - ((unsigned __int64)(v6 + 1) & 0xFFFFFFFFFFFFFFF8ui64))),
      8i64 * (((unsigned int)v6 - (((_DWORD)v6 + 8) & 0xFFFFFFF8) + 272) >> 3));
    *(_QWORD *)ClassName = 'C\0S\0C\0K';        // kcsc :D
    v18 = 'G\0y\0e\0K';
    v19 = 'l\0C\0n\0e';
    v20 = 's\0s\0a';
    *(_QWORD *)&WndClass.style = 0i64;
    *(_QWORD *)&WndClass.cbClsExtra = 0i64;
    *(_OWORD *)&WndClass.hbrBackground = 0i64;
    WndClass.lpfnWndProc = (WNDPROC)WinProc;
    WndClass.hInstance = hInstance;
    WndClass.lpszClassName = ClassName;
    WndClass.hCursor = LoadCursorW(0i64, (LPCWSTR)0x7F00);
    WndClass.hbrBackground = 0i64;
    WndClass.hIcon = LoadIconW(0i64, (LPCWSTR)0x7F00);
    v7 = RegisterClassW(&WndClass);
    result = 0;
    if ( v7 )
    {
      Window = CreateWindowExW(
                 1u,
                 ClassName,
                 L"KCSC Software Activation",   // /omg
                 0xCA0000u,
                 0x80000000,
                 0x80000000,
                 520,
                 310,
                 0i64,
                 0i64,
                 hInstance,
                 0i64);
      result = 0;
      if ( Window )
      {
        ShowWindow(Window, nShowCmd);
        UpdateWindow(Window);
        GetWindowRect(Window, &Rect);
        v10 = Rect.right - Rect.left;
        v11 = Rect.bottom - Rect.top;
        SystemMetrics = GetSystemMetrics(0);
        v13 = GetSystemMetrics(1);
        SetWindowPos(Window, 0i64, (SystemMetrics - v10) / 2, (v13 - v11) / 2, 0, 0, 5u);
        while ( GetMessageW(&Msg, 0i64, 0, 0) > 0 )
        {
          TranslateMessage(&Msg);
          DispatchMessageW(&Msg);
        }
        return Msg.wParam;
      }
    }
  }
  else
  {
    MessageBoxW(0i64, L"Failed to initialize!", L"Error", 0x10u);
    return 0;
  }
  return result;
}
``` 
- Ta thấy chương trình đang xử lý 1 vùng nhớ Bigdata nào đó, thật ra nó đang cố decode vùng nhớ này thành các Opcodes, sau đó thực hiện chúng trong thời gian thực, hàm `WinProc` và việc Debugging động cũng sẽ cũng có quan điểm này của mình. 
- Tiếp theo, ngắm `WinProc` ta chỉ nên quan tâm bắt đầu từ phần này vì những phần khác chỉ ra râu ria tạo Giao diện linh tinh: 
```cpp!
 if ( (_WORD)a3 == 3 && (a3 & 0xFFFF0000) == 0 )
    {
      GetWindowTextA(qword_7FF7912B0F50, String, 256);// Lấy User + Seri
      GetWindowTextA(qword_7FF7912B0F48, Str.m128i_i8, 256);
      if ( String[0] && Str.m128i_i8[0] )
      {
        if ( Dynamic_FlagcheckFunc__
          && (unsigned int)((__int64 (__fastcall *)(CHAR *, __m128i *, __int16 *))Dynamic_FlagcheckFunc__)(
                             String,
                             &Str,
                             SMT_SUS) == 1 )    // Dynamic flagcheckfunc()
        {
          qmemcpy(chText, byte_7FF7912A6020, sizeof(chText));
          v22 = strlen(Str.m128i_i8);
          sub_7FF7912921BD((__int64)&Str, v22, chText, 280);
          v26 = 0;
          sub_7FF7912A53B0(Buffer, 0x400ui64, (wchar_t *)Format, chText);
          put_result_fail_or_success_(hWndParent, Buffer, 1);
        }
        else
        {
          v21 = sub_7FF791292242((__int64)&unk_7FF7912A61A0, dword_7FF7912A6140);
          sub_7FF7912A53B0(Buffer, 0x400ui64, (wchar_t *)Format, v21);
          put_result_fail_or_success_(hWndParent, Buffer, 0);
        }
        return 0i64;
      }
      else
      {
        MessageBoxW(hWndParent, L"Please enter both Username and Serial Key!", "I", 0x30u);
        return 0i64;
      }
    }
  }
```
- Có 1 con trỏ hàm, mình sẽ đặt tên nó là `Dynamic_FlagcheckFunc`. Khi debug động thì hàm này sẽ trỏ vào 1 vùng code được Gen Runtime và mình tin là sau khi được chế biến từ `Bigdata`.
- Dường như `Dynamic` sẽ xử lý gì đó với Input là `username và seri`, nếu đúng thì sẽ chạy vào cái hàm `put_result` cho ta.

Bắt đầu Debug động, mình sẽ đặt Breakpoint tại đoạn gọi `Dynamic_FlagcheckFunc`, Stepin để coi nó làm gì: 
```asm!
debug042:0000021815EB0000 test    rdx, rdx                        ; rdx cầm Seri, sau sẽ đưa cho rbx
debug042:0000021815EB0003 push    rbp
debug042:0000021815EB0004 setz    al
debug042:0000021815EB0007 test    r8, r8                          ; r8 = SMT_SUS
debug042:0000021815EB000A push    rdi
debug042:0000021815EB000B push    rsi
debug042:0000021815EB000C push    rbx                             ; đoạn này kiểm tra coi username và seri có rỗng ko
debug042:0000021815EB000D mov     rbx, rdx
debug042:0000021815EB0010 setz    dl
debug042:0000021815EB0013 or      al, dl
debug042:0000021815EB0015 jnz     loc_21815EB0108
debug042:0000021815EB001B xor     eax, eax
debug042:0000021815EB001D test    rcx, rcx                        ; rcx cầm username
debug042:0000021815EB0020 jz      loc_21815EB0108
debug042:0000021815EB0026
debug042:0000021815EB0026 loc_21815EB0026:                        ; CODE XREF: debug042:0000021815EB0035↓j
debug042:0000021815EB0026 cmp     byte ptr [rcx+rax], 0           ; check lenght username, ko cần qtam vì nó hardcoded
debug042:0000021815EB002A jz      short loc_21815EB0037
debug042:0000021815EB002C inc     rax
debug042:0000021815EB002F cmp     rax, 256
debug042:0000021815EB0035 jnz     short loc_21815EB0026           ; check lenght username, ko cần qtam vì nó hardcoded
debug042:0000021815EB0037
debug042:0000021815EB0037 loc_21815EB0037:                        ; CODE XREF: debug042:0000021815EB002A↑j
debug042:0000021815EB0037 xor     edx, edx
debug042:0000021815EB0039
debug042:0000021815EB0039 loc_21815EB0039:                        ; CODE XREF: debug042:0000021815EB0049↓j
debug042:0000021815EB0039 cmp     byte ptr [rbx+rdx], 0
debug042:0000021815EB003D jz      short loc_21815EB0053           ; kiểm tra Seri lenght (max 256b)
debug042:0000021815EB003F inc     rdx
debug042:0000021815EB0042 cmp     rdx, 100h
debug042:0000021815EB0049 jnz     short loc_21815EB0039
debug042:0000021815EB004B mov     r11d, 100h
debug042:0000021815EB0051 jmp     short loc_21815EB0056
debug042:0000021815EB0053 ; ---------------------------------------------------------------------------
debug042:0000021815EB0053
debug042:0000021815EB0053 loc_21815EB0053:                        ; CODE XREF: debug042:0000021815EB003D↑j
debug042:0000021815EB0053 mov     r11d, edx
debug042:0000021815EB0056
debug042:0000021815EB0056 loc_21815EB0056:                        ; CODE XREF: debug042:0000021815EB0051↑j
debug042:0000021815EB0056 test    r11d, r11d
debug042:0000021815EB0059 jz      loc_21815EB0108
debug042:0000021815EB005F cmp     eax, 0Ch
debug042:0000021815EB0062 jnz     loc_21815EB0108
debug042:0000021815EB0068 xor     eax, eax
debug042:0000021815EB006A cmp     byte ptr [rcx], 6Bh ; 'k'
debug042:0000021815EB006D jnz     loc_21815EB010A
debug042:0000021815EB0073 cmp     byte ptr [rcx+2], 61h ; 'a'
debug042:0000021815EB0077 jnz     loc_21815EB010A
debug042:0000021815EB007D cmp     byte ptr [rcx+4], 33h ; '3'
debug042:0000021815EB0081 jnz     loc_21815EB010A
debug042:0000021815EB0087 cmp     byte ptr [rcx+6], 33h ; '3'
debug042:0000021815EB008B jnz     short loc_21815EB010A
debug042:0000021815EB008D cmp     byte ptr [rcx+8], 35h ; '5'
debug042:0000021815EB0091 jnz     short loc_21815EB010A
debug042:0000021815EB0093 cmp     byte ptr [rcx+0Ah], 6Eh ; 'n'
debug042:0000021815EB0097 jnz     short loc_21815EB010A
debug042:0000021815EB0099 cmp     byte ptr [rcx+1], 6Dh ; 'm'
debug042:0000021815EB009D jnz     short loc_21815EB010A
debug042:0000021815EB009F cmp     byte ptr [rcx+3], 72h ; 'r'
debug042:0000021815EB00A3 jnz     short loc_21815EB010A
debug042:0000021815EB00A5 cmp     byte ptr [rcx+5], 76h ; 'v'
debug042:0000021815EB00A9 jnz     short loc_21815EB010A
debug042:0000021815EB00AB cmp     byte ptr [rcx+7], 72h ; 'r'
debug042:0000021815EB00AF jnz     short loc_21815EB010A
debug042:0000021815EB00B1 cmp     byte ptr [rcx+9], 21h ; '!'
debug042:0000021815EB00B5 jnz     short loc_21815EB010A
debug042:0000021815EB00B7 cmp     byte ptr [rcx+0Bh], 39h ; '9'   ; username = hardcoded (kmar3v3r5!n9)
debug042:0000021815EB00BB jnz     short loc_21815EB010A
debug042:0000021815EB00BD xor     r10d, r10d                      ; r10d = biến lặp i
debug042:0000021815EB00C0 mov     r9d, 1337h                      ; r9d_prev = 0x1337, esi = 12 = usernamelenght
debug042:0000021815EB00C6 mov     esi, 12
debug042:0000021815EB00CB
debug042:0000021815EB00CB loc_21815EB00CB:                        ; CODE XREF: debug042:0000021815EB00FF↓j
debug042:0000021815EB00CB mov     eax, r10d
debug042:0000021815EB00CE movsx   ebp, byte ptr [rbx+r10]         ; seri[i]
debug042:0000021815EB00D3 cdq
debug042:0000021815EB00D4 idiv    esi                             ; edx = i % 12
debug042:0000021815EB00D6 mov     eax, r9d                        ; chuyển r9d -> eax
debug042:0000021815EB00D9 xor     ebp, r9d                        ; seri[i] ^ r9_prev
debug042:0000021815EB00DC movsxd  rdx, edx                        ; rdx = i %12
debug042:0000021815EB00DF movzx   edi, byte ptr [rcx+rdx]         ; edi = username[i % 12]
debug042:0000021815EB00E3 xor     edx, edx
debug042:0000021815EB00E5 div     esi                             ; edx = r9d_prev % 12
debug042:0000021815EB00E7 xor     edi, ebp                        ; edi = username[i%12] ^ (seri[i] ^ r9_prev)
debug042:0000021815EB00E9 mov     edx, edx
debug042:0000021815EB00EB movzx   r9d, byte ptr [rcx+rdx]
debug042:0000021815EB00F0 add     r9d, edi                        ; r9d_now = username[i%12] ^ (seri[i] ^ r9_prev) + username[r9d_prev%12]
debug042:0000021815EB00F3 cmp     [r8+r10*4], r9d                 ; SMT_SUS[i] phải bằng r9d_now
debug042:0000021815EB00F7 jnz     short loc_21815EB0108
debug042:0000021815EB00F9 inc     r10
debug042:0000021815EB00FC cmp     r11d, r10d
debug042:0000021815EB00FF jg      short loc_21815EB00CB
debug042:0000021815EB0101 mov     eax, 1
debug042:0000021815EB0106 jmp     short loc_21815EB010A
```
- Có vẻ như đoạn code đang check `username` (mặc định phải là `kmar3v3r5!n9` không thì cúc) và đồng thời check `seri` bằng cách: 
```
Loop:
SMT_SUS[i] = username[i%12] ^ (seri[i] ^ r9_prev) + username[r9d_prev%12] 

r9_prev = SMT_SUS[i - 1]
``` 
- `i` bắt đầu chạy từ 0 
- `r9` bắt đầu khởi tạo = 1337h
- `SMT_SUS` là bảng Target Table được tạo sẵn, ta chỉ lấy Blob này ra. 

Trong quá trình debug mình còn phát hiện ra bài sẽ lấy Seri làm key (+ Padding = clone) để giải mã Flag theo dạng RC4: 
![image](https://hackmd.io/_uploads/rkf1pZ6GWg.png)
![image](https://hackmd.io/_uploads/HyIWT-pz-g.png)
> Nhưng bản chất ta giải được Seri là đủ ra flag rồi...

Hiểu được điều này, dường như việc gen username và seri key vô cùng đơn giản: 

```python!
SMT = [
0x1350,0x13E9,0x13FC,0x1400,0x1453,0x1489,0x14F7,0x1529,
0x15A0,0x15DF,0x166D,0x1677,0x169B,0x1733,0x17B8,0x1803,
0x1875,0x1858,0x188D,0x18F3,0x1905,0x199D,0x1A39,0x1AC8,
0x1AC1,0x1AB9,0x1B23,0x1B6E,0x1B9A,0x1C2F,0x1CBB,0x1D36,
0x1DA8,0x1E02,0x1EA2,0x1EFF,0x1F27,0x1F90,0x1FEC,0x201E,
0x20D9,0x20BB,0x212D,0x212E,0x21A4,0x2200,0x228D,0x2366,
0x239F,0x0110
]
USERNAME = b'kmar3v3r5!n9' 

def gen_serial(SMT_SUS, username):
    serial = []
    r9_prev = 0x1337
    uname_len = len(username)
    for i, r9 in enumerate(SMT_SUS):
        u1 = username[i % uname_len]
        u2 = username[r9_prev % uname_len]
        temp = (r9 - u2) & 0xFFFFFFFF
        temp = temp ^ u1
        s_byte = (temp ^ r9_prev) & 0xFF
        serial.append(s_byte)
        r9_prev = r9 & 0xFFFFFFFF
    return bytes(serial)
def main():
    serial = gen_serial(SMT, USERNAME)
    print(serial)

if __name__ == "__main__":
    main()
    
#KCSC-2026-JU57-R341-UX0R-4ndd-U4DD-W!TH-U53R-N4M3%
```
Đoạn này khá bựa, kết quả ra được dính quả % ở cuối, khi nhập cả cái seri này thì chương trình gen FakeFlag và chỉ gen FlagRiel khi bỏ `%` đi (Khả năng mình dump sai Blob SMT_SUS) :vvv. 

Màn hình hiện Gen Flag thành công và cho ta:
`VXpCT1ZGRXpkRVJOUnpRMVkycFNNR1JVUlRCa1EwVjNZbXBXWmxkVVFqRllNR2N3WkdwT1psVXpWbXBaZWsweFRsZGFNVTFVUmpWWWVsSnFaRU5HTWs1SVVUQmtSRTVyV0hwa2IwMHhPVTFKVjAxNlltcFZlbGd4VFhkYWJsSXpUa2hKZWxneFkyaGtSMmhtVld0Tk1GaDZVblZhUmpsRlRUSk5kMXBFVG1aUmJVWjZXbFJaTUZoNlRtWldRMFowVFROT1psaDZNSEJMVTJ3NQ==`
Vứt lên `decoder` rồi b64decode 2 lần là ra Flag: 
>Thế mà 1 lúc mình mới biết đấy :vvv, bài này nói chung rút thời gian mình đi hơi nhiều do tự nhiên gặp mấy lỗi trông đần như này...

![image](https://hackmd.io/_uploads/r1Keyv3MWl.png)
# 33,550,337 (medium): 
Câu này cho ta 1 file `.apk` và ghi nhét nó vào các trình phân tích thì mình thấy rằng nó bị làm rối khá nặng, mình làm khá ít bài về `.apk` nên cũng hơi rối vì những bài mình làm chủ yếu Logic nó ở `MainActivities` hết rồi :v 

Ngồi ngẩn ngơ 1 lúc thì mình nghĩ ra 1 cách đó là `Hack` trực tiếp apk...

Công cụ mình dùng không xa lạ với anh em có tuổi thơ mod mấy game offline để bug vô hạn tiền mạng hay máu các thứ... Đó là `Game Guardian`!

Đầu tiên cài Tool và file `.apk` vào máy ảo, mình chọn NOX Player: 
- Mở App và Tool, ở màn hình chính có vẻ như App cần ta nhấp đủ 33 triệu lần mới nhả flag :v 
![image](https://hackmd.io/_uploads/rkcpbvhGZe.png)
- Nhấp 3 lần, rồi mở Tool: 
![Screenshot 2025-12-14 231237](https://hackmd.io/_uploads/rksZMv2GZx.png)
![Screenshot 2025-12-14 231250](https://hackmd.io/_uploads/Byi-GP2Mbe.png)
- Có vẻ như nó tìm được khá nhiều giá trị 3 trong process hiện tại, nhấp thêm vài lần rồi tìm Cycle progress bằng Tool 1 lần nữa... 
![image](https://hackmd.io/_uploads/H1BSfw2G-e.png)
- Ô có vẻ như chỉ còn 1 giá trị tương ứng với Cycle hiện tại, sửa nó thành 999999999 là ngon!
- ![image](https://hackmd.io/_uploads/Sk2OfDnzWg.png)

Flag kìa!!!
# Flag in Wonderland (tobecontinue)
Đề bài là 1 challenge Flagchecker được viết bằng ngôn ngữ Gameboy cổ, để có thể reverse bài này mình sử dụng 2 tool đó là `bgb` và GhidraBoy. 
![image](https://hackmd.io/_uploads/r1aMeFBQbg.png)
- Cụ thể bài cần ta nhập đúng flag thôi :v 

Sau khi nạp file lên GhidraBoy thì mình nhanh chóng đi tìm cái dòng `Enterflag` nằm ở đâu để tìm manh mối xử lý Input, khi mình tìm được thì mình nhảy vào được 1 hàm trông như này: 
> Mình đã đổi tên biến và hàm theo ý hiểu của mình.
![image](https://hackmd.io/_uploads/Skt8lYHQZg.png)
![image](https://hackmd.io/_uploads/r19wxtrmZl.png)

Cụ thể tại sao mình viết biến key chính là mảng được XOR với OxCC đó chính là khi debug bằng `bgb`, mình có để ý ở phần đầu WRAM có 1 dòng như này: 
![image](https://hackmd.io/_uploads/S16CgKrX-x.png)
- Key bắt đầu từ C0F6 cũng chính là giá trị được show trên GhidraBoy. 
- Đồng thời mình cũng bắt được Ciphertext nằm ở ngay đầu WRAM do hàm `MEMCMP` mình đoán ở dưới có truyền vào địa chỉ C0D6. 
- Sau khi chạy dần vòng lặp XOR ở đầu hàm thì mình thấy cái vùng mình nói là Key biến đổi dần dần rồi thành như này: ![image](https://hackmd.io/_uploads/BJ9TZtH7bg.png)

Được rồi, vì là bài flag checker và String được compare với Input là 1 chuỗi byte dị dị như này thì mình đoán đề bài sẽ thử mã hóa Input rồi so sánh với Ciphertext xem có giống nhau hay không, nếu không giống thì trả về 0, từ đó thoát chương trình: ![image](https://hackmd.io/_uploads/H1FdGKHQbx.png)

## Maybe_Sbox: 
```CPP!

void Maybe_Sbox(int a1,int a2)

{
  char *pcVar1;
  byte bVar2;
  uint in_AF;
  char cVar3;
  int i;
  code *UNRECOVERED_JUMPTABLE;
  undefined2 local_8;
  undefined2 local_2;
  
  local_8 = 0;
  i = 0;
  do {
                    /* S[i] = i */
    *(char *)(a2 + i) = (char)i;
    i = i + 1;
                    /* chia dư 256 */
  } while ((char)((uint)i >> 8) == '\0');
  local_2 = 0;
  do {
                    /* j % lenKey (inAF chính là trạng thái của thanh ghi  khi mới bắt
                       đầu hàm) */
    i = FUN_059c(in_AF >> 8,local_2);
    bVar2 = (char)local_8 + *(char *)(a2 + local_2) + *(char *)(a1 + i);
    local_8 = (uint)bVar2;
    cVar3 = *(char *)(local_8 + a2);
    pcVar1 = (char *)(a2 + (uint)(byte)(*(char *)(a1 + i) + cVar3 + bVar2));
    *(char *)(local_8 + a2) = *pcVar1;
    *pcVar1 = cVar3;
    if ((char)((char)local_2 + '\x01') == '\0') {
      local_2._1_1_ = local_2._1_1_ + '\x01';
    }
    local_2 = CONCAT11(local_2._1_1_,(char)local_2 + '\x01');
  } while (local_2._1_1_ == '\0');
                    /* WARNING: Could not recover jumptable at 0x02bb. Too many branches */
                    /* WARNING: Treating indirect jump as call */
  (*UNRECOVERED_JUMPTABLE)((char)((uint)a2 >> 8),UNRECOVERED_JUMPTABLE,local_2);
  return;
}
``` 
Mình thử dựa vào đoạn code rồi đoán rằng đây là 1 hàm tạo Sbox trông như này: 
```python!
maybe_Sbox: 
	S[] = [i for in range(256)] 
	for j in range(256): 
	i = j % keyLen 
	local8 = local8 + S[j] + key[i] 
	idx = key[i] + local8 + S[local8] 
	S[local8], S[idx] = S[idx], S[local8]
```
## Supa_Crypto 
Đây mới chính là cái khó của bài, tại phần code có phần tham số khá là Lmao khi cụ thể ngoài hàm có thể chỉ truyền vào 3 tham số nhưng khi vào trong đọc logic hàm thì lại thấy nó nhận đến 6 tham số... Được hint của các anh là đọc phần source code asm và debug động thì đây là những gì mình rút ra: 

```cpp!
undefined2 Supa_Crypto(int param_1,byte *Sbox,int param_3,byte param_4,char param_5,byte param_6 )

{
  char cVar1;
  byte *pbVar2;
  byte bVar3;
  byte m;
  undefined2 unaff_retaddr;
  byte i;
  byte local_c;
  byte j;
  undefined2 n;
  
  i = 0;
  j = 0;
  local_c = *Sbox;
  bVar3 = 0;
  n = 0;
  while (n._1_1_ < (byte)(param_5 + ((byte)n < param_4))) {
    for (m = 0; m < param_6; m = m + 1) {
      i = i + 1;
      pbVar2 = Sbox + i;
      bVar3 = *pbVar2;
      j = j + bVar3;
      *pbVar2 = Sbox[j];
      Sbox[j] = bVar3;
      bVar3 = Sbox[(byte)(*pbVar2 + bVar3)];
    }
    local_c = local_c ^ bVar3 ^ *(byte *)(n + param_1);
    *(byte *)(n + param_3) = local_c;
    cVar1 = (byte)n + 1;
    n = CONCAT11(n._1_1_,cVar1);
    if (cVar1 == '\0') {
      n = (uint)(byte)(n._1_1_ + 1) << 8;
    }
  }
                    /* WARNING: Treating indirect jump as return */
  return unaff_retaddr;
}
``` 

Cụ thể đầu tiên tại sao lai có tận 6 tham số, minh xin được giải thích như sau: 
- `param1, param3`: Tham số này mình đoán dựa trên cách đề bài làm việc với chúng và hint rằng đây là mã hóa RC4 Custom, `param1` mình đoán nó là source và `param3` sẽ là dest dựa trên việc tính toán được thực hiện trên `param1` rồi lưu vào vùng nhớ của `param3`.
- `param2`: Đây chính là địa chỉ Sbox được tạo ra trước khi chạy hàm này. Mình có 2 manh mối để đưa ra kết luận này: 
    - Nếu tin tưởng vào pseudo Ghidra, có thể Maybe_Sbox truyền Sbox lên stack tại vùng nhớ auStack165, ta có thể thấy pattern này lặp lại ở lần mã hóa tiếp theo:![image](https://hackmd.io/_uploads/B1EsSFrQ-e.png)
    - Để ý nếu như param2 là Sbox thì đoạn code mã hóa trông khá giống PRGA: ![image](https://hackmd.io/_uploads/B1pVIYH7Zl.png)
- `param 4 và 5` mình đoán chúng lần lượt có thể là 0 và 0x20, tại sao? 
    - ![image](https://hackmd.io/_uploads/H1xl_KHQbx.png)
    ![image](https://hackmd.io/_uploads/H1IM_YHQWx.png)
    - Ta thấy rằng tham số được truyền vào hàm qua cơ chế push stack, stack được push BC trong đó B là byte cao còn C là byte thấp lưu giá trị là 0x20, trong code có `(n._1_1_ < (byte)(param_5 + ((byte)n < param_4)))` bản chất chỉ đang so sánh n với 32 nằm trong BC: 
- `param6`: Đây mình đoán là tham số đầu tiên được đẩy vào hàm, với lần mã hóa đầu tiên `param6` có giá trị là 0x17:
    ![image](https://hackmd.io/_uploads/Hkq4YKSXbe.png)
    ![image](https://hackmd.io/_uploads/H13rKtHm-g.png)
Vậy trông đoạn code sẽ được mình mô phỏng kiểu như này: 
```python!
PRGA: 
	i = 0 
	j = 0 
	n = 0 
	temp = 0 
	while(maybe: n <= 32): 
		for m in range(param_6): 
			i = i + 1 & 0xFF 
			j = j + S[i] & 0xFF	 
			S[i], S[j] = S[j], S[i] & 0xFF 
			temp = S[S[j] + S[i]] & 0xFF 
	
		c = c ^ temp ^ src[n] & 0xFF
		dest[n] = c 
```
## Kết luận: 
Vậy dựa trên pseudo và dự đoán của mình, thì giờ mình chỉ cần mò `param6` ứng với từng lần gọi `Supa_Crypto` thì ta dường như có thể giải ra flag, cụ thể ta thấy rằng Input sẽ bị đem đi mã hóa `Maybe_Sbox + Supa_Crypto` 3 lần, nên ta sẽ giải mã Cipher 3 lần ứng với 3 cái `param6`. 
- Param6-1 = 0x17, 
- Param6-2 = 0x20 
![image](https://hackmd.io/_uploads/H1RucKrX-x.png)
- Param6-3 = 0x37 
    - Mình mò không được nên thử lấy tham số cuối được truyền vào hàm `Supa_Crypto` cuối thì vẫn đúng là Param6-3 riel: 

```python!
def decrypt(ciphertext, key, param6):
    S = list(range(256))
    key_len = len(key)
    local8 = 0
    for j in range(256):
        i = j % key_len
        local8 = (local8 + S[j] + key[i]) & 0xFF
        idx = (key[i] + local8 + S[local8]) & 0xFF
        S[local8], S[idx] = S[idx], S[local8]

    i = 0
    j = 0
    prev_cipher_byte = S[0] 
    plaintext = [0] * len(ciphertext)
    for n in range(len(ciphertext)):
        temp = 0
        for m in range(param6):
            i = (i + 1) & 0xFF
            j = (j + S[i]) & 0xFF
            S[i], S[j] = S[j], S[i]
            temp= S[(S[i] + S[j]) & 0xFF]
        current_cipher_byte = ciphertext[n]
        plaintext[n] = (prev_cipher_byte ^ temp ^ current_cipher_byte) & 0xFF
        prev_cipher_byte = current_cipher_byte

    return bytes(plaintext)

cipher = bytes.fromhex('ED4E69BE8449C676D7B70CA3061B4BE2115B4151D737D8B82B3F047D9664A1')
key = b't0j1t4_h34rt_n1_fur3t41'
l1 = decrypt(cipher, key, 0x37) 
l2 = decrypt(l1, key, 0x20)     
l3 = decrypt(l2, key, 0x17)    
print(f"flag: {l3}")
```

Flag: **KCSC{t0r4w4r3_n0_f41ry_t4l3!!!!}**
# easy_obf (hard): 
> Hơi tiếc câu này mình không giải được trong quá trình tham gia giải, cách của mình nếu đủ trâu bò thì sẽ giải ra thui nhưng mà lúc đó mình đã oải + cố gắng tìm cách khác mà không được :< 
> 
![image](https://hackmd.io/_uploads/ryMQplb7Zx.png)
## Phân tích: 
Nạp file đề bài vào ida thì việc nhìn pseudo sẽ là không khả quan do mã nguồn đã bị làm rối (đúng như des của đề: `Don't trust your disassembler !!!`): 
![image](https://hackmd.io/_uploads/Syp_FMbm-x.png)
Để nhìn được Logic gốc ta phải chuyển góc nhìn sang phần assembly: 
![image](https://hackmd.io/_uploads/SygJ5z-mWe.png)
Có thể thấy rằng mã bao gồm rất nhiều các block: 
```asm 
push   Address
mov    [rsp + 4], 1
retn
```

Ta đều biết rằng, trong asm mỗi khi caller gọi tới 1 hàm callee, nó sẽ đẩy địa chỉ instruction tiếp theo lên stack để sau khi calee chạy xong thì lệnh retn chỉ cần pop địa chỉ ở đỉnh stack và gán nó cho RIP thôi... Nói cách khác, bài sử dụng chính cơ chế này của `retn - C3` để làm rối không cho IDA gen Pseudo cũng như là cách để điều khiển luồng cho chương trình -> `PUSH Address -> RETN == JUMP Address`. 
Cụ thể, 1 block điều luồng có dạng như trên sẽ đẩy value là `địa chỉ`, nhưng trong x64 lệnh push chỉ đẩy lên stack một giá trị 32bit, vì vậy sau khi đẩy address lên stack block sẽ còn có 1 đoạn `mov    [rsp + 4], 1` giúp điền nốt giá trị của địa chỉ cần nhảy tới. 
Hiểu được cách làm rối của đề, cách mình làm sẽ là trace liên tục các `jump` trá hình này, nhặt các instruction rồi sau đó thử ghép chúng lại để tìm manh mối logic của đề bài. 
### Diving Deeper... 
Chạy đề trên cmd: 
```
PS C:\Users\duong\Downloads> .\easy_of.exe
Usage: <chall.exe> <flag>
``` 
Test: 
```
PS C:\Users\duong\Downloads> .\easy_of.exe "KCSC{hihihi}"
Wrong!
```
Dựa vào kết quả ta biết rằng đây là 1 bài theo dạng Flagchecker, việc cần làm bây giờ là mò xem đoạn checkflag hay ít nhất là đoạn nào đang sờ mó đến flag hoặc input... 

Dưới đây là toàn bộ các đoạn instruction mà mình trâu bò trace và ghi lại được trong quá trình giải bài: https://docs.google.com/document/d/1C987Txjl8AluU_A7PaMc_LgglrqBEmSPovfmskq9swk/edit?usp=sharing (Dài quá nên phải cho lên docs :(((. Flow sẽ là từ trên xuống dưới không bị ngắt quảng quay ngược về trước (mình trải phẳng Loop rùi...)

Có code rồi thì ta thử phân tích coi chương trình thực sự đang làm cái quái gì nhé: 

### Analyzing codes: 
Instruction đầu tiên mà ta gặp đó chính là: 
```asm
.upx0:0000000140006598 sub     rsp, 3D8h
``` 
- Đây theo mình là những dòng code đầu tiên của Logic chính, nó lấy 1 vùng nhớ khá lớn để bắt đầu xây dựng logic, vùng nhớ cần sử dụng, ... 

Tiếp theo, tại Blob này dường như chương trình đang điền gì đó vào stack (Thẻ 2): 
![image](https://hackmd.io/_uploads/rkDZnXbQZe.png)
Cụ thể logic gán của đoạn này như sau: 
```asm!
mov     eax, 0Ch
imul    rax, i        (bắt đầu từ 0, mỗi khi gán value 3 lần cho vùng [rax + rcx] thì tăng i lên 1 đơn vị. Có thể đoạn này khởi tạo mảng hay ma trận gì đó).
lea     rax, [rsp+rax+Offset] 
mov     ecx, 4 
imul    ecx, 0        (tăng dần từ 0 -> 2)
mov     dword ptr [rax+rcx], value
``` 

- Dựa vào logic thì ta có thể tách được 2 mảng được tạo bằng Logic này, 1 cái bắt đầu từ: `[rsp + rax + 178h]` chứa khoảng 9 ký tự (3 * 3) và 1 cái bắt đầu từ `[rsp + rax + F8h]`: 
- Ta sẽ được 2 mảng nếu được dựng thành ma trận trông chúng sẽ như này: 
```python
# Ma trận 3x3 
matrix = [
    [0x06, 0x18, 0x01],
    [0x0D, 0x10, 0x0A],
    [0x14, 0x11, 0x0F]
]
# Ciphertext dump được
cipher = [
    0xE9, 0xAF, 0x12, 0x2D, 0x6B, 0x44, 0x51, 0xEC, 
    0x49, 0x5B, 0xD1, 0x3E, 0x8F, 0xEA, 0x56, 0x1A, 
    0x94, 0x11, 0x12, 0xEC, 0xC2, 0xA4, 0xDA, 0x9D
]
```

Tiếp theo, chương trình dường như đang load các strings lên stack (Thẻ 3: Load Strings) theo mình tìm hiểu được những strings API hay strings được đề bài in ra sẽ được đẩy lên stack để tránh bị các công cụ phân tích tĩnh như IDA dò được và cho vào mục `.data` hay bị phát hiệ bởi `strings` hay các công cụ phân tích tĩnh đơn giản. 
- Chương trình đẩy các strings là các WindowAPI như: `LoadLibraryA (Để nạp các file .all), GetProcAddress (Tìm địa chỉ các API khác dựa vào tên đã nạp trong Stack), GetStdHandle (Quản lý I/O bàn phím màn hình), WriteConsoleA, GetCommandLineW và CommandLineToArgvW (Cái này dùng để tách Tham số truyền vào chương trình hay tách Input để kiểm tra`. 
- Kèm theo đó là các Strings tương tác mà author viết như `Usage, Wrong, Correct, KCSC{...}`. Nhìn vào strings ta nhận ra có thể đề bài sẽ chỉ nhận thân flag thôi chứ phần `KCSC{}` không được xét mà sẽ được gói lại bằng chương trình về sau. 

Sau khi load các strings, chương trình sẽ tiến hành xây dựng Buffer1[256] và Buffer2[256] (chúng được khởi tạo rỗng bằng các giá trị 0). `sub_140006000` chính là hàm gì đó có chức năng tạo Buffer. Code sẽ gọi hàm này 2 lần, tạo 2 Buffer1 và 2 ở 2 vùng nhớ: [rsp + 1D0h] và [rsp + 2D0h]. (Thẻ 4)

Sau khi setup Buffer, chương trình gọi đến hàm xử lý Windows API như `loc_14000C08E+2` sẽ tìm địa chỉ `kernel32.32`, cụ thể:
```asm!
mov     rax, gs:60h    ; Trong x64 trên thanh ghi đoạn GS tại Offset 60h trỏ tới PEB (Process Environment Block)
mov     rax, [rax+18h]    ; Tại Offset 18h của PEB trỏ tới cấu trúc PEB_LDR_DATA, nó lưu trữ danh sách các .dll đã được nạp vào chương trình 
.upx0:00000001400060DE 48 8B 40 20                                     mov     rax, [rax+20h]    ; Tại offset 20h của PEB_LDR_DATA là LinkedList chứa các module theo thứ tự được lưu vòa bộ nhớ. 
mov rax, [rax]  ; Đi tới node tiếp theo (ntdll.dll) 
mov     rax, [rax]    ; Đi vào node tiếp (kernel32.dll) 
mov rax, [rax+20h]    ;Trong cấu trúc LDR_DATA_TABLE_ENTRY, khi duyệt bằng InMemoryOrderLinks (nằm ở offset 0x10 của struct), thì trường DllBase (địa chỉ cơ sở của DLL) nằm ở offset 0x30.Khoảng cách từ 0x10 đến 0x30 chính là 0x20 -> Lệnh này lấy địa chỉ gốc của kernel32.dll đưa vào rax 
``` 
Sau khi tìm địa chỉ của kernel32.dll thì nhảy đến hàm trông khá là rối, dường như nó đang cố tìm địa chỉ API trong `Export Table (GetProcAddress manual implementation)`. Cụ thể qua hàm `word_14000C11A+2`, tham số được truyền vào chính là địa chỉ của `kernel32.all` được tìm thấy ban nãy và string `LoadLibraryA`![image](https://hackmd.io/_uploads/BkNUjDZQZe.png)

Tại sao mình biết được thì trong lúc trace thì mình bị dính vào 1 cái loop chạy rất lâu và khi quan sát bộ nhớ thì tên các API được cập nhật liên tục, còn phần code thì quá khó hiểu nên mình chỉ hiểu sơ sơ: 
![image](https://hackmd.io/_uploads/BkypjvW7bx.png)

Tiếp, chương trình bắt đầu gọi dần các API khi đã setup xong: 
```asm!
call    qword ptr [rsp+140h] // Gọi tới LoadLibraryA
call    qword ptr [rsp+1A8h] // Gọi tới GetStdHandle 
call    qword ptr [rsp+1B0h] // Gọi tới: kernelbase_GetCommandLineW
call    qword ptr [rsp+1B8h]  // CommandLineToArgvW    ; Đây chính là API sẽ cắt lấy Input của ta.
```
Đằng sau còn có: 
```asm!
.upx0:0000000140007077 48 89 84 24 70 01 00 00                         mov     [rsp+170h], rax
.upx1:0000000140009F8D 83 BC 24 58 01 00 00 02                         cmp     dword ptr [rsp+158h], 2
.upx2:000000014000CFBD 0F 84 10 00 00 00                               jz      loc_14000CFD3 (có nhảy vào đây) 
.upx2:000000014000D010 B8 08 00 00 00                                  mov     eax, 8
.upx0:00000001400070D5 48 6B C0 01                                     imul    rax, 1
.upx1:0000000140009FE9 48 8B 8C 24 70 01 00 00                         mov     rcx, [rsp+170h]
.upx2:000000014000D025 48 8B 0C 01                                     mov     rcx, [rcx+rax]
.upx0:00000001400070E9 FF 94 24 C0 01 00 00                            call    qword ptr [rsp+1C0h] //lstrlenW
.upx1:000000014000A001 89 84 24 34 01 00 00                            mov     [rsp+134h], eax
.upx2:000000014000D039 83 BC 24 34 01 00 00 17                         cmp     dword ptr [rsp+134h], 23
```
- Đoạn này sẽ kiểm tra xem ta có nhập Input hay không và Input phải có đọ dài là 23 (ta nắm được độ dài flag). Ta cũng biết luôn Flag nằm ở đoạn `mov     rcx, [rsp+170h]` (để ý phần này trong asm một chút ta sẽ thấy địa chỉ này lưu string từ byte thứ 8 or argv[1]).

Tiếp theo, chương trình xử lý Input ta nhập thông qua vòng lặp, chuyển nén dữ liệu từ 2 byte còn 1 byte: 
```asm!
.upx0:0000000140007153 C7 44 24 34 00 00 00 00                         mov     dword ptr [rsp+34h], 0 
.upx2:000000014000D0A9 8B 84 24 34 01 00 00                            mov     eax, [rsp+134h]
.upx0:000000014000717D 39 44 24 34                                     cmp     [rsp+34h], eax 
.upx1:000000014000A07F 0F 8D 10 00 00 00                               jge     loc_14000A095 (không nhảy vào cái này)
.upx2:000000014000D0C0 B8 08 00 00 00                                  mov     eax, 8
.upx0:0000000140007191 48 6B C0 01                                     imul    rax, 1
.upx1:000000014000A0A5 48 63 4C 24 34                                  movsxd  rcx, dword ptr [rsp+34h]
.upx2:000000014000D0D5 48 8B 94 24 70 01 00 00                         mov     rdx, [rsp+170h]
.upx0:00000001400071A5 48 8B 04 02                                     mov     rax, [rdx+rax]
.upx1:000000014000A0BA 0F B7 04 48                                     movzx   eax, word ptr [rax+rcx*2]
.upx2:000000014000D0ED 25 FF 00 00 00                                  and     eax, 0FFh
.upx0:00000001400071B9 48 63 4C 24 34                                  movsxd  rcx, dword ptr [rsp+34h]
.upx1:000000014000A0CE 88 84 0C D0 01 00 00                            mov     [rsp+rcx+1D0h], al
.upx2:000000014000D095 8B 44 24 34                                     mov     eax, [rsp+34h]
.upx0:000000014000716B FF C0                                           inc     eax
.upx1:000000014000A06B 89 44 24 34                                     mov     [rsp+34h], eax
.upx2:000000014000D0A9 8B 84 24 34 01 00 00                            mov     eax, [rsp+134h]
.upx0:000000014000717D 39 44 24 34                                     cmp     [rsp+34h], eax
.upx1:000000014000A07F 0F 8D 10 00 00 00                               jge     loc_14000A095
... Lặp lại Logic này: Dường như nó chuyển  flagừ 2 byte còn 1 byte rồi lưu lần lượt vào Buffer trên Stack 
``` 
- Cụ thể `[rsp+34h] là biến đếm i, mỗi lần chạy nó sẽ được so sánh với len(Input) được lưu ở [rsp + 134h] mà ta biết được ở hàm trước`. Ta để ý mỗi khi logic thực hiện xong i sẽ được tăng bằng cách `mov     eax, [rsp+34h] -> inc eax -> mov     [rsp+34h], eax`. 
- Logic bên trong Loop này là nén các ký tự từ 2 bit xuống 1 bit (Trên Windows sử dụng chuẩn UTF-16 cho các hàm có hậu tố `CommandLineToArgvW - được nạp và sử dụng trong bài`): 
```asm!
movsxd  rcx, dword ptr [rsp+34h]
.upx2:000000014000D0D5 48 8B 94 24 70 01 00 00                         mov     rdx, [rsp+170h]
.upx0:00000001400071A5 48 8B 04 02                                     mov     rax, [rdx+rax]
.upx1:000000014000A0BA 0F B7 04 48                                     movzx   eax, word ptr [rax+rcx*2]
.upx2:000000014000D0ED 25 FF 00 00 00                                  and     eax, 0FFh
.upx0:00000001400071B9 48 63 4C 24 34                                  movsxd  rcx, dword ptr [rsp+34h]
.upx1:000000014000A0CE 88 84 0C D0 01 00 00                            mov     [rsp+rcx+1D0h], al
``` 
- rdx cầm chuỗi, rcx cầm biến đếm lặp `i`, duyệt 1 ký tự nó được tăng gấp đôi giá trị (do đọc dword), các giá trị đọc được lấy byte thấp thông qua `and 0FFh` rồi lưu vào buffer `[rsp + 1D0h]` mà ta đã tạo từ trước. 

Cuối cùng, sau khi đã duyệt nén các ký tự, ta đến với logic check Flag chính của bài: 
```asm!
.upx0:00000001400071E3 4C 8D 8C 24 78 01 00 00                         lea     r9, [rsp+178h]    ;Nếu còn nhớ thì đây chính là địa chỉ của cái ma trận 3x3 đề xây dựng lúc đầu.
.upx1:000000014000A0F9 41 B8 18 00 00 00                               mov     r8d, 18h     ; len(Input tính cả '\0')
.upx2:000000014000D12A 48 8D 94 24 D0 02 00 00                         lea     rdx, [rsp+2D0h]    ; Buffer đã được tọa từ trước nhưng đang trống, có thể kết quả của hàm xử lý input cuối cùng sẽ được ném vào đây
.upx0:00000001400071FB 48 8D 8C 24 D0 01 00 00                         lea     rcx, [rsp+1D0h]    ; Đoạn Input đã được xử lý 
```
Nhìn vào Thẻ 6: End Game trong link docs: 
```asm!
.upx1:00000001400093A4 C7 44 24 08 00 00 00 00                         mov     dword ptr [rsp+8], 0    ; Biến đếm i 
.upx0:000000014000643A 8B 44 24 30                                     mov     eax, [rsp+30h]
.upx1:00000001400093CF 39 44 24 08                                     cmp     [rsp+8], eax 

... 

.upx0:0000000140006426 8B 44 24 08                                     mov     eax, [rsp+8]
.upx1:00000001400093BC 83 C0 03                                        add     eax, 3
.upx2:000000014000C40C 89 44 24 08                                     mov     [rsp+8], eax
.upx0:000000014000643A 8B 44 24 30                                     mov     eax, [rsp+30h]
.upx1:00000001400093CF 39 44 24 08                                     cmp     [rsp+8], eax 

; Đoạn này là vòng lặp ngoài cùng, tăng i lên 3 và kiểm tra xem nó có < 24 hay không 
``` 
- Dường như đây là 3 Loop lồng nhau, đi sâu nữa coi ... 
```asm!
.upx2:000000014000C420 0F 8D 10 00 00 00                               jge     loc_14000C436 
.upx0:000000014000644E C7 44 24 04 00 00 00 00                         mov     dword ptr [rsp+4], 0
.upx2:000000014000C45A 83 7C 24 04 03                                  cmp     dword ptr [rsp+4], 3
.upx0:0000000140006478 0F 8D 10 00 00 00                               jge     loc_14000648E 
.upx1:0000000140009407 C7 44 24 0C 00 00 00 00                         mov     dword ptr [rsp+0Ch], 0     ; sum 
.upx2:000000014000C46F C7 04 24 00 00 00 00                            mov     dword ptr [rsp], 0
.upx1:0000000140009432 83 3C 24 03                                     cmp     dword ptr [rsp], 3
.upx2:000000014000C498 0F 8D 10 00 00 00                               jge     loc_14000C4AE 
Đoạn này sẽ tạo các biến lặp j (0 -> 2) và k (0 -> 2)
```

```asm!
.upx0:00000001400064C1 48 63 44 24 04                                  movsxd  rax, dword ptr [rsp+4]
.upx1:0000000140009446 48 6B C0 0C                                     imul    rax, 0Ch
.upx2:000000014000C4BE 48 8B 4C 24 38                                  mov     rcx, [rsp+38h]
.upx0:00000001400064D6 48 03 C8                                        add     rcx, rax
.upx1:000000014000945A 48 8B C1                                        mov     rax, rcx

Đoạn này lấy j*12 nghĩa là nó trỏ thẳng vào đầu hàng của ma trận 3x3 lưu ở [rsp+38h] (38h - 18h == 20h -> Nơi lưu 3X3 đã được khai báo đầu hàm này)
```

```asm!
.upx0:00000001400064E9 8B 14 24                                        mov     edx, [rsp]
.upx1:000000014000946D 44 8B 44 24 08                                  mov     r8d, [rsp+8]
.upx2:000000014000C4E7 44 03 C2                                        add     r8d, edx
.upx0:00000001400064FC 41 8B D0                                        mov     edx, r8d
.upx1:0000000140009482 48 63 D2                                        movsxd  rdx, edx 
.upx2:000000014000C4FA 4C 8B 44 24 20                                  mov     r8, [rsp+20h]
.upx0:000000014000650F 41 0F B6 14 10                                  movzx   edx, byte ptr [r8+rdx]
Đoạn này lấy i + k (Hay vị trí ký tự Input cần lấy). Rồi sau đó load Input vào r8 rồi lấy giá trị ở vị trí Input[i+k]

... 

.upx1:0000000140009495 8B 04 88                                        mov     eax, [rax+rcx*4]    ; Matrix[j][k]
.upx2:000000014000C50F 0F AF C2                                        imul    eax, edx
.upx0:0000000140006524 8B 4C 24 0C                                     mov     ecx, [rsp+0Ch]
.upx1:00000001400094A8 03 C8                                           add     ecx, eax    ; add sum
.upx2:000000014000C522 8B C1                                           mov     eax, ecx
.upx0:0000000140006538 89 44 24 0C                                     mov     [rsp+0Ch], eax

Đoạn này tiếp tục lấy rax = Matrix[k][j] đem nhân với edx là Input rối cộng dồn vào sum...

Có thể thấy ngay đây chính là nhân ma trận chính hiệu!
```

Vậy Logic của bài toán có thể là như sau: Chương trình sẽ chặt Input của ta ra thành 3 mẩu mỗi mẩu khoảng 8 byte rồi xếp nó như 1 ma trận với 3 dòng tương đương với 3 mẩu. Ta được cho 1 Ma trận 3x3, ma trận còn lại mình coi là Ciphertext từ flag và vẻ đúng như mình dự đoán, ta cần nhập Input sao cho: 
![image](https://hackmd.io/_uploads/ryoOiK-mZg.png)

Do ta đã dump được Matrix và Ciphertext, mình có thể bruteforce luôn flag: 
## Solution
```python!
# Ma trận 3x3
matrix = [
    [0x06, 0x18, 0x01],
    [0x0D, 0x10, 0x0A],
    [0x14, 0x11, 0x0F]
]

# Ciphertext dump được
cipher = [
    0xE9, 0xAF, 0x12, 0x2D, 0x6B, 0x44, 0x51, 0xEC, 
    0x49, 0x5B, 0xD1, 0x3E, 0x8F, 0xEA, 0x56, 0x1A, 
    0x94, 0x11, 0x12, 0xEC, 0xC2, 0xA4, 0xDA, 0x9D
]

def solve_block(m, c_block):
    for i in range(256):
        for j in range(256):
            for k in range(256):
                res0 = (m[0][0]*i + m[0][1]*j + m[0][2]*k) % 256
                res1 = (m[1][0]*i + m[1][1]*j + m[1][2]*k) % 256
                res2 = (m[2][0]*i + m[2][1]*j + m[2][2]*k) % 256
                if [res0, res1, res2] == c_block:
                    return [i, j, k]
    return [0, 0, 0]

flag = ""
for b in range(0, len(cipher), 3):
    block = cipher[b:b+3]
    decoded = solve_block(matrix, block)
    flag += "".join(chr(x) for x in decoded)

print(f"flag: {flag}")
```
Flag: **KCSC{easy_obfuscate_0328db2e}**
> Bài này còn có cách khác nhanh hơn, mình có thể sẽ cập nhật trong tương lai ...