---
# System prepended metadata

title: WannaGame CyberKnight

---

# WannaGame CyberKnight
## BLUEPRINT:


Đề cho ta 1 file `blueprint.exe` và 1 file `user.json.enc`

Vì không cẩn thận nên mình đã double click lên file .exe đó ngay tại laptop của mình => Kết quả là BSOD xuất hiện (màn hình xanh chết chóc). Đây cũng là lí do vì sao mình chọn chall này làm (vì nó trông khá giống malware), trong quá trình làm bài mình đã hỏi ChatGPT, Claude, stalk github người khác về những câu tương tự, xem những cuộc trò chuyện trên forum cũ như `Tuts4You` hay là `https://www.hvaonline.net/` để hiểu thêm về kĩ thuật được sử dụng trong bài này, ngoài ra mình cũng đã cố gắng hỏi anh little timmy và anh ấy chỉ cho mình 1 số trang web để mình tự `research`, ngoài ra anh còn đưa ra 1 suggest cực kì hữu ích cho mình (dù ban đầu mình đọc chả hiểu cm gì cả).
![image](https://hackmd.io/_uploads/Skhh5yMZlg.png)


Tổng quan thì challenge cho 1 file thực thi `.exe`, load vào bất kì trình disassembler (IDA Pro, Ghidra, Binja,...) cũng cho ta thấy đây là 1 chương trình bị `obfuscated`. Không có `strings` nào thực sự hữu ích có thể nhìn  dc liền để đoán ra `flow` của chương trình. Check hàm `main` cũng cho thấy code bị làm rối khá nhiều và không có `flow` thực sự rõ ràng ngay lập tức. Đặc biệt liên quan tới kĩ thuật chính của bài này thì không có cái gọi `hàm API` hay `strings` nào có thể cung cấp manh mối tốt về việc chương trình đang làm gì.

Hàm main được `IDA` đưa ra bên dưới: 

<details open>
    <summary>Main:</summary>

```C
int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v4; // r12
  __int64 v5; // r13
  __int64 v6; // rax
  _BYTE *v7; // r15
  _BYTE *v8; // rsi
  _QWORD *v9; // rcx
  struct _LIST_ENTRY *i; // rax
  struct _LIST_ENTRY *Flink; // r10
  struct _LIST_ENTRY *v12; // rdi
  __int64 v13; // r9
  char *v14; // rdx
  int v15; // r8d
  char v16; // cl
  char *v17; // rdx
  struct _LIST_ENTRY *v18; // rcx
  FILE *v19; // rsi
  struct _LIST_ENTRY *v22; // rax
  struct _LIST_ENTRY *v23; // r11
  struct _LIST_ENTRY *v24; // rdi
  __int64 v25; // r9
  char *v26; // rcx
  int v27; // r8d
  char v28; // dl
  char *v29; // rcx
  _BYTE *v30; // rsi
  size_t v31; // rsi
  void *v32; // rax
  void *v33; // rcx
  _QWORD *v34; // rax
  char *v35; // rbx
  __int64 v36; // rax
  _BYTE *v37; // rdi
  _QWORD *v38; // rcx
  FILE *v39; // r14
  struct _LIST_ENTRY *v42; // rax
  struct _LIST_ENTRY *v43; // r11
  struct _LIST_ENTRY *v44; // rdi
  __int64 v45; // r9
  char *v46; // rdx
  int v47; // r8d
  char v48; // cl
  char *v49; // rdx
  size_t v50; // rdi
  void *v51; // rax
  void *v52; // rcx
  _QWORD *v53; // rax
  char *v54; // rbx
  _BYTE *v55; // rax
  _BYTE *v56; // rax
  __int64 v58[4]; // [rsp+40h] [rbp+0h] BYREF

  _RBP = (unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64;
  *(_OWORD *)(_RBP + 104) = 0i64;
  v4 = 0i64;
  *(_QWORD *)(_RBP + 120) = 0i64;
  *(_OWORD *)(_RBP + 128) = 0i64;
  sub_1400016A0(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 128, argv, envp);
  *(_OWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x50) = 0i64;
  v5 = 0i64;
  *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x60) = 0i64;
  *(_QWORD *)_RBP = 0x60B7F02F67E80D38i64;
  *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x40) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
  *(_QWORD *)_RBP = 0x66A934904CD0C7C8i64;
  *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x48) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
  *(_QWORD *)_RBP = 0xFC49A01158D7E4Di64;
  *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA0) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
  *(_QWORD *)_RBP = 0x66A934904CD0C7A6i64;
  *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA8) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
  *(__m128 *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x40) = _mm_xor_ps(
                                                                           (__m128)_mm_load_si128((const __m128i *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 64)),
                                                                           *(__m128 *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64)
                                                                                     + 0xA0));
  v6 = sub_1400052D0(_RBP + 32, _RBP + 64);
  if ( ((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 104 == v6 )
  {
    v8 = *(_BYTE **)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x70);
    v7 = *(_BYTE **)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x68);
  }
  else
  {
    v7 = *(_BYTE **)v6;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x68) = *(_QWORD *)v6;
    v8 = *(_BYTE **)(v6 + 8);
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x70) = v8;
    v4 = *(_QWORD *)(v6 + 16);
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x78) = v4;
    *(_QWORD *)v6 = 0i64;
    *(_QWORD *)(v6 + 8) = 0i64;
    *(_QWORD *)(v6 + 16) = 0i64;
  }
  v9 = *(_QWORD **)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20);
  if ( v9 )
  {
    if ( *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) - (_QWORD)v9 >= 0x1000ui64 )
    {
      v9 = (_QWORD *)*(v9 - 1);
      if ( (unsigned __int64)(*(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) - (_QWORD)v9 - 8i64) > 0x1F )
        invalid_parameter_noinfo_noreturn();
    }
    j_j_free(v9);
    *(_OWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0i64;
  }
  for ( i = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink; ; i = i->Flink )
  {
    Flink = i[3].Flink;
    v12 = (struct _LIST_ENTRY *)((char *)Flink + *(unsigned int *)((char *)&Flink[8].Blink + SHIDWORD(Flink[3].Blink)));
    if ( v12 != Flink )
    {
      LODWORD(v13) = v12[1].Blink;
      if ( (_DWORD)v13 )
        break;
    }
LABEL_16:
    ;
  }
  while ( 1 )
  {
    v13 = (unsigned int)(v13 - 1);
    v14 = (char *)Flink + *(unsigned int *)((char *)&Flink->Flink + 4 * v13 + LODWORD(v12[2].Flink));
    v15 = -913135182;
    v16 = *v14;
    v17 = v14 + 1;
    if ( v16 )
    {
      do
      {
        v15 = 16777619 * (v15 ^ v16);
        v16 = *v17++;
      }
      while ( v16 );
      if ( v15 == -2119732292 )
        break;
    }
    if ( !(_DWORD)v13 )
      goto LABEL_16;
  }
  v18 = (struct _LIST_ENTRY *)((char *)Flink
                             + *(unsigned int *)((char *)&Flink->Flink
                                               + 4
                                               * *(unsigned __int16 *)((char *)&Flink->Flink
                                                                     + 2 * v13
                                                                     + HIDWORD(v12[2].Flink))
                                               + HIDWORD(v12[1].Blink)));
  if ( ((__int64 (__fastcall *)(struct _LIST_ENTRY *, char *))v18)(v18, v17) )
    sub_140002BA0();
  if ( v7 == v8 )
  {
    v19 = _acrt_iob_func(1u);
    *(_QWORD *)_RBP = 0x2FB7F32170E1170Bi64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x14C614E938A0AAC3i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0xA9FFDF1B8C9E65A7ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x8C69E9C23F599708ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0xFC49A01158D7E4Di64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA0) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x66A934904CD0C7A6i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA8) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0xCE91B67FEDFB1787ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xB0) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x8C0D8CAE5638F128ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xB8) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    __asm
    {
      vmovdqu ymm0, ymmword ptr [rbp+0F0h+Block]
      vpxor   ymm1, ymm0, [rbp+0F0h+var_50]
      vmovdqa ymmword ptr [rbp+0F0h+Block], ymm1
    }
    v22 = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink;
    __asm { vzeroupper }
    while ( 1 )
    {
      v23 = v22[3].Flink;
      v24 = (struct _LIST_ENTRY *)((char *)v23 + *(unsigned int *)((char *)&v23[8].Blink + SHIDWORD(v23[3].Blink)));
      if ( v24 != v23 )
      {
        LODWORD(v25) = v24[1].Blink;
        if ( (_DWORD)v25 )
          break;
      }
LABEL_27:
      v22 = v22->Flink;
    }
    while ( 1 )
    {
      v25 = (unsigned int)(v25 - 1);
      v26 = (char *)v23 + *(unsigned int *)((char *)&v23->Flink + 4 * v25 + LODWORD(v24[2].Flink));
      v27 = 1855580234;
      v28 = *v26;
      v29 = v26 + 1;
      if ( v28 )
      {
        do
        {
          v27 = 16777619 * (v27 ^ v28);
          v28 = *v29++;
        }
        while ( v28 );
        if ( v27 == 1566996403 )
          break;
      }
      if ( !(_DWORD)v25 )
        goto LABEL_27;
    }
    ((void (__fastcall *)(unsigned __int64, __int64, __int64, FILE *))((char *)v23
                                                                     + *(unsigned int *)((char *)&v23->Flink
                                                                                       + 4
                                                                                       * *(unsigned __int16 *)((char *)&v23->Flink + 2 * v25 + HIDWORD(v24[2].Flink))
                                                                                       + HIDWORD(v24[1].Blink))))(
      _RBP + 32,
      1i64,
      32i64,
      v19);
    v30 = *(_BYTE **)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x50);
    goto LABEL_65;
  }
  *(_OWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA0) = 0i64;
  *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xB0) = 0i64;
  v31 = v8 - v7;
  if ( v31 )
  {
    if ( v31 > 0x7FFFFFFFFFFFFFFFi64 )
      sub_1400064E0();
    if ( v31 < 0x1000 )
    {
      v34 = operator new(v31);
    }
    else
    {
      if ( v31 + 39 <= v31 )
        sub_140001190();
      v32 = operator new(v31 + 39);
      v33 = v32;
      if ( !v32 )
LABEL_43:
        invalid_parameter_noinfo_noreturn();
      v34 = (_QWORD *)(((unsigned __int64)v32 + 39) & 0xFFFFFFFFFFFFFFE0ui64);
      *(v34 - 1) = v33;
    }
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA0) = v34;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA8) = v34;
    v35 = (char *)v34 + v31;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xB0) = (char *)v34 + v31;
    memmove(v34, v7, v31);
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA8) = v35;
  }
  v36 = sub_140001950(_RBP + 128, _RBP + 32, _RBP + 160);
  if ( _RBP + 80 == v36 )
  {
    v37 = *(_BYTE **)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x58);
    v30 = *(_BYTE **)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x50);
  }
  else
  {
    v30 = *(_BYTE **)v36;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x50) = *(_QWORD *)v36;
    v37 = *(_BYTE **)(v36 + 8);
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x58) = v37;
    v5 = *(_QWORD *)(v36 + 16);
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x60) = v5;
    *(_QWORD *)v36 = 0i64;
    *(_QWORD *)(v36 + 8) = 0i64;
    *(_QWORD *)(v36 + 16) = 0i64;
  }
  v38 = *(_QWORD **)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20);
  if ( v38 )
  {
    if ( *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) - (_QWORD)v38 >= 0x1000ui64 )
    {
      v38 = (_QWORD *)*(v38 - 1);
      if ( (unsigned __int64)(*(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) - (_QWORD)v38 - 8i64) > 0x1F )
        goto LABEL_43;
    }
    j_j_free(v38);
    *(_OWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0i64;
  }
  if ( v30 == v37 )
  {
    v39 = _acrt_iob_func(1u);
    *(_QWORD *)_RBP = 0x66B0EA7867EE1008i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x3C55DF12AF0A9C9i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0xCE91B67FEDFB17E3ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x8C0D8CAE5638F128ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0xFC49A01158D7E4Di64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA0) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x66A934904CD0C7A6i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA8) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0xCE91B67FEDFB1787ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xB0) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x8C0D8CAE5638F128ui64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xB8) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    __asm
    {
      vmovdqu ymm0, ymmword ptr [rbp+0F0h+Block]
      vpxor   ymm1, ymm0, [rbp+0F0h+var_50]
      vmovdqa ymmword ptr [rbp+0F0h+Block], ymm1
    }
    v42 = NtCurrentPeb()->Ldr->InLoadOrderModuleList.Flink;
    __asm { vzeroupper }
    while ( 1 )
    {
      v43 = v42[3].Flink;
      v44 = (struct _LIST_ENTRY *)((char *)v43 + *(unsigned int *)((char *)&v43[8].Blink + SHIDWORD(v43[3].Blink)));
      if ( v44 != v43 )
      {
        LODWORD(v45) = v44[1].Blink;
        if ( (_DWORD)v45 )
          break;
      }
LABEL_53:
      v42 = v42->Flink;
    }
    while ( 1 )
    {
      v45 = (unsigned int)(v45 - 1);
      v46 = (char *)v43 + *(unsigned int *)((char *)&v43->Flink + 4 * v45 + LODWORD(v44[2].Flink));
      v47 = 408384500;
      v48 = *v46;
      v49 = v46 + 1;
      if ( v48 )
      {
        do
        {
          v47 = 16777619 * (v47 ^ v48);
          v48 = *v49++;
        }
        while ( v48 );
        if ( v47 == -2053918735 )
          break;
      }
      if ( !(_DWORD)v45 )
        goto LABEL_53;
    }
    ((void (__fastcall *)(unsigned __int64, __int64, __int64, FILE *))((char *)v43
                                                                     + *(unsigned int *)((char *)&v43->Flink
                                                                                       + 4
                                                                                       * *(unsigned __int16 *)((char *)&v43->Flink + 2 * v45 + HIDWORD(v44[2].Flink))
                                                                                       + HIDWORD(v44[1].Blink))))(
      _RBP + 32,
      1i64,
      18i64,
      v39);
  }
  else
  {
    sub_140002FF0();
    *(_OWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0i64;
    v50 = v37 - v30;
    if ( v50 )
    {
      if ( v50 > 0x7FFFFFFFFFFFFFFFi64 )
        sub_1400064E0();
      if ( v50 < 0x1000 )
      {
        v53 = operator new(v50);
      }
      else
      {
        if ( v50 + 39 <= v50 )
          sub_140001190();
        v51 = operator new(v50 + 39);
        v52 = v51;
        if ( !v51 )
          invalid_parameter_noinfo_noreturn();
        v53 = (_QWORD *)(((unsigned __int64)v51 + 39) & 0xFFFFFFFFFFFFFFE0ui64);
        *(v53 - 1) = v52;
      }
      *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = v53;
      *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = v53;
      v54 = (char *)v53 + v50;
      *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = (char *)v53 + v50;
      memmove(v53, v30, v50);
      *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = v54;
    }
    *(_QWORD *)_RBP = 0x60B7F02F67E80D38i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x40) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x66A934F322B5E9C8i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x48) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0xFC49A01158D7E4Di64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA0) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(_QWORD *)_RBP = 0x66A934904CD0C7A6i64;
    *(_QWORD *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0xA8) = *(_QWORD *)((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64);
    *(__m128 *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64) + 0x40) = _mm_xor_ps(
                                                                             (__m128)_mm_load_si128((const __m128i *)(_RBP + 160)),
                                                                             *(__m128 *)(((unsigned __int64)v58 & 0xFFFFFFFFFFFFFFE0ui64)
                                                                                       + 0x40));
    sub_140005670(_RBP + 64, _RBP + 32);
  }
LABEL_65:
  if ( v30 )
  {
    v55 = v30;
    if ( (unsigned __int64)(v5 - (_QWORD)v30) >= 0x1000 )
    {
      v30 = (_BYTE *)*((_QWORD *)v30 - 1);
      if ( (unsigned __int64)(v55 - v30 - 8) > 0x1F )
        invalid_parameter_noinfo_noreturn();
    }
    j_j_free(v30);
  }
  if ( v7 )
  {
    v56 = v7;
    if ( (unsigned __int64)(v4 - (_QWORD)v7) >= 0x1000 )
    {
      v7 = (_BYTE *)*((_QWORD *)v7 - 1);
      if ( (unsigned __int64)(v56 - v7 - 8) > 0x1F )
        invalid_parameter_noinfo_noreturn();
    }
    j_j_free(v7);
  }
  return 0;
}
```
</details>

Bài này mình sẽ dùng `Binary Ninja` là chủ yếu, lí do là vì hệ thống `Intermediate Language` của nó + GUI hiện đại, dễ dùng hơn IDA/Ghidra với mình thì cảm giác như đang sử dụng `VSCode`. 

Ok bắt đầu mò sâu hơn vô trong hàm  thì ta tìm thấy những `số random` xuất hiện rất nhiều. Cái này này lặp lại rất ở nhiều nơi trong chương trình. Mình sẽ trích ra 1 đoạn:

```c
...
140005a24        __builtin_memset(&s_1, 0, 0x18);
140005a29        int64_t r13 = 0;
140005a42        int64_t var_c0 = 0x60b7f02f67e80d38;
140005a58        int64_t var_b8 = 0x66a934904cd0c7c8;
140005a6e        void* const s_3 = 0xfc49a01158d7e4d;
140005a87        int64_t var_58 = 0x66a934904cd0c7a6;
140005a9a        var_c0 ^= s_3;
140005aa7        void* s;
140005aa7        int64_t* s_4 = sub_1400052d0(&s, &var_c0);
...
```

### String "user.jso":

Sau khi tìm ra đoạn code trên thì mình sẽ kiểm tra phép toán `XOR` giữa các số này và nó thực sự trả về cho mình 1 chuỗi có thể đọc được.

Dùng Python theo lệnh sau: 

```py
>>> (0x60b7f02f67e80d38^0xfc49a01158d7e4d).to_bytes(8, "little")
b'user.jso'
>>> (0x66a934904cd0c7c8^0x66a934904cd0c7a6).to_bytes(8, "little")
b'n\x00\x00\x00\x00\x00\x00\x00'
```

+ `8`: là length hay số byte ta muốn tạo ra.
+ `little`: là thứ tự byte xuất hiện, ở đây sẽ là ít nhất ở trước. 


Không hiểu vì sao `user.json` lại bị mất chữ `n` ở cuối, nhưng điều đó không quan trọng, ta đã xác định được nó chính là file gốc bị mã hóa rồi. Chuỗi `user.json` được truyền vào hàm `sub_1400052d0` như là 1 tham số thứ hai. Mở hàm này lên xem thử nó làm cái gì? Và ngay từ đầu hàm lại xuất hiện `obfuscated`...

### String "rb":

![image](https://hackmd.io/_uploads/BJaPEgGbge.png)

Thực hiện `XOR` như trên: 
```py
>>> (0xfc49a01158d1c3f^0xfc49a01158d7e4d).to_bytes(8, "little")
b'rb\x00\x00\x00\x00\x00\x00'
```

Kết quả là chuỗi `rb`: nghĩa là đọc file ở dạng nhị phân (read binary).

Cùng suy luận 1 chút: 
+ Chuỗi `user.json` được khôi phục từ XOR => là tên file.
+ Chuỗi `rb` cũng được khôi phục từ XOR => là mode mở file.
+ Hai giá trị đó lần lượt được truyền vào hàm `sub_1400052d0`.

=> hàm `sub_1400052d0` có thể đang làm việc kiểu: 

```
FILE *f = fopen("user.json", "rb");
```

Nhờ tra GPT mà mình biết `rb` lại còn là tham số điển hình khi dùng [fopen](https://cplusplus.com/reference/cstdio/fopen/) trong C/C++ nữa. 

### Pattern quan trọng để thu thập `_IMAGE_EXPORT_DIRECTORY`:

Tiếp tục với hàm `sub_1400052d0`, ta thấy một pattern quen thuộc lại xuất hiện – và pattern này lặp đi lặp lại ở nhiều nơi trong toàn bộ chương trình.



![image](https://hackmd.io/_uploads/ByuT4lfbee.png)


#### PEB và LDR_DATA_TABLE_ENTRY

Tổng quan thì lúc này thì chương trình duyệt qua danh sách các `module` đã load của tiến trình hiện tại. Để làm đc điều này thì nó cần truy cập vô `Process Environment Block` (PEB). Mình thực sự không hề có khái niệm về PEB cho tới khi làm bài này, mình đã bỏ phần lớn thời gian chall này chỉ để đi đọc + hỏi ChatGPT/Claude rất rất nhiều về [nó](https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb). 


Thông tin về các `module` được chứa trong vùng `loader data`, dưới dạng một `linked list` gồm các dạng kiểu [LDR_DATA_TABLE_ENTRY](https://github.com/conix-security/zer0m0n/blob/master/src/driver/include/nt/structures/LDR_DATA_TABLE_ENTRY.h). 

Ta nên chỉnh lại code 1 chút, vì khi disassembler (như Binary Ninja, IDA, hay Ghidra) phân tích code, nó không biết những vùng nhớ như `rbx_2`, `rdx_1`, `r10_1` đại diện cho cái gì. Chúng chỉ là thanh ghi hoặc con trỏ trong suy nghĩ của tools thôi.


+ Nên là mình định nghĩa struct mới trong Binary Ninja cho `_LDR_DATA_TABLE_ENTRY`, rồi gán kiểu của `Flink` thành `_LDR_DATA_TABLE_ENTRY*`. 
Đoạn này mình đang đề cập tới `doubly linked list` trong Windows. Trong danh sách liên kết đôi (doubly linked list), mỗi node (mỗi phần tử trong danh sách) sẽ có hai con trỏ:

    + Flink: Trỏ đến node tiếp theo trong danh sách.

    + Blink: Trỏ đến node trước đó trong danh sách.

Cấu trúc này cho phép ta dễ dàng di chuyển qua lại giữa các node trong list, vì ta có thể duyệt cả về phía trước lẫn phía sau. Tạo điều kiện thuận lợi cho việc thao tác với các module đã được tải trong bộ nhớ của hệ điều hành.

Nếu bạn dùng disassembler khác thì có thể bạn sẽ cần chỉnh, nhưng Binja thì khá 'thông minh' trong khoản này, nó có thể tự động phân tích và gán tên biến, kiểu dữ liệu, thậm chí là cả struct cho các thành phần thường thấy trong Windows. (Thật ra mình không dùng Binja ngay lúc đầu mà vô tình chọn phải, còn vì sao mình biết điều trên á? Vì mình đã tìm được cái [này](https://apps.dtic.mil/sti/pdfs/AD1126216.pdf) trong discord của W1, đây là report về các tool RE thường được sử dụng, nếu rảnh thì đọc nó cũng 0 phải ý tồi.)
![image](https://hackmd.io/_uploads/S1MhSgfbge.png)


+  Khi phân tích một `DLL` trên hệ thống Windows, đầu tiên ta sẽ thấy rằng nó bắt đầu với một `_IMAGE_DOS_HEADER`, đây là header đầu tiên của file PE. Tiếp theo, chương trình sẽ đọc vào `_IMAGE_NT_HEADERS64` với một offset là `0x88`. Offset này chỉ tới phần `data dictionary` trong `IMAGE_OPTIONAL_HEADER64`, nơi chứa thông tin về các mục export của `DLL`. Cụ thể, đây là cấu trúc `_IMAGE_EXPORT_DIRECTORY`, nơi chứa các thông tin quan trọng như danh sách các tên hàm được export, địa chỉ của các hàm đó, và các chỉ mục của chúng.

Mục tiêu chính của mình trong đoạn này là truy cập vào thông tin trong `_IMAGE_EXPORT_DIRECTORY` thông qua `Export Table`. Khi có được các dữ liệu này, ta  sẽ biết `DLL` xuất những hàm gì, và sau đó có thể gọi các hàm đó từ các chương trình khác.

Thay đổi kiểu dữ liệu của một vài biến sẽ cho kết quả dễ đọc và rõ ràng hơn so với trước. Đôi khi các tools như trên không thể tự động nhận diện tất cả các kiểu dữ liệu đúng đc, và ta cần định nghĩa lại kiểu dữ liệu của các biến sao cho make sense hơn. Nhờ làm cnay mà giúp tools hiểu đúng các data structures, từ đó làm cho output trở nên dễ đọc + hiểu hơn. 


![image](https://hackmd.io/_uploads/rJETBgzbxx.png)



#### Dllbase:


Xét code trên, ta thấy dòng này: 
![image](https://hackmd.io/_uploads/Bk2prxMZle.png)
  + `Flink` như đã nói ở trên là một con trỏ trong danh sách các module đang tải (trong `LDR_DATA_TABLE_ENTRY`), mỗi module được quản lý trong linked list. 
  + `0x30` là offset để lấy `DllBase`, tức là địa chỉ bộ nhớ nơi DLL được tải vào. Vậy, `r10_1` chính là địa chỉ bắt đầu của DLL (DllBase).
=> Ta rename `r10_1` thành `DllBase`


Code đã được rename: 

```C
...
14000538a        struct _LDR_DATA_TABLE_ENTRY* Flink =
14000538a            gsbase->ProcessEnvironmentBlock->Ldr->InLoadOrderModuleList.Flink;
14000538a        
140005390        while (true)
140005390        {
140005390            struct _IMAGE_DOS_HEADER* DllBase = Flink->DllBase;
1400053a0            struct _IMAGE_EXPORT_DIRECTORY* rbx_2 =
1400053a0                (uint64_t)*(uint32_t*)((int64_t)DllBase->e_lfanew + DllBase + 0x88)
1400053a0                + DllBase;
1400053a0            
1400053a6            if (rbx_2 != DllBase)
1400053a6            {
...
```

### Fields quan trọng trong _IMAGE_EXPORT_DIRECTORY:

Trước khi tiếp tục thì mình đã đọc thêm về `_IMAGE_EXPORT_DIRECTORY`. Tổng quan nó là một cấu trúc trong Windows PE, được sử dụng để lưu trữ thông tin về các hàm và dữ liệu mà DLL xuất (export). Một số trường trong cấu trúc này bao gồm thông tin về địa chỉ các tên hàm, địa chỉ các hàm thực tế, số lượng hàm xuất,...

Các trường chính trong `_IMAGE_EXPORT_DIRECTORY`:
+ `NumberOfNames (tại offset 0x18)`: Đây là số lượng tên hàm mà DLL xuất.

Ví dụ, nếu một DLL xuất 10 hàm, thì giá trị của trường này sẽ là 10.

+ `AddressOfNames`: Là địa chỉ tới một danh sách các con trỏ, mỗi con trỏ trỏ tới tên của một hàm xuất.

Cùng đi tiếp tới đoạn code tiếp theo: 

```C
1400053a8                uint64_t r9_1 = (uint64_t)*(uint32_t*)((char*)rbx_2 + 0x18);
1400053a8                
1400053af                while (r9_1)
1400053af                {
1400053c0                    r9_1 = (uint64_t)(r9_1 - 1);
1400053cb                    void* rdx_1 = (uint64_t)*(uint32_t*)(
1400053cb                        (uint64_t)*(uint32_t*)((char*)rbx_2 + 0x20) + (r9_1 << 2) + r10_1)
1400053cb                        + r10_1;
```

![image](https://hackmd.io/_uploads/BJEyUlG-lx.png)
+ `(char*)rbx_2 + 0x18`: Di chuyển con trỏ `rbx_2` tới offset `0x18` (tức là trường `NumberOfNames` trong `_IMAGE_EXPORT_DIRECTORY`).
+ ``*(uint32_t*)((char*)rbx_2 + 0x18)``: Lấy giá trị tại địa chỉ đó, chính là số lượng hàm xuất (NumberOfNames).
+ `uint64_t r9_1`: Lưu giá trị này vào biến `r9_1`, tức là số lượng các hàm xuất trong DLL.
=> `r9_1` được dùng để lưu số lượng các hàm xuất mà DLL cung cấp. Thế nên là rename biến này thành `NumberOfNames` cho dễ đọc 

Thế `AdressOfNames` thì sao? Nếu `NumberOfNames` = 5, thì tại địa chỉ tương ứng với `AddressOfNames`, ta sẽ có 5 con trỏ 4 byte (tức 5 địa chỉ) .

![image](https://hackmd.io/_uploads/H14lUxG-xe.png)

Ở đây `rbx_2` được hiểu đơn thuần là một con trỏ kiểu void* hoặc char*, và đang dc truy cập như một `block memory` thô v (``+ 0x20`).

Nhưng offset `0x20` trong `_IMAGE_EXPORT_DIRECTORY` chính là `AddressOfNames`.

Vì sao thì nó theo định nghĩa chuẩn của `_IMAGE_EXPORT_DIRECTORY` theo Microsoft thôi. 

```
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD Characteristics;         // 0x00
    DWORD TimeDateStamp;           // 0x04
    WORD  MajorVersion;            // 0x08
    WORD  MinorVersion;            // 0x0A
    DWORD Name;                    // 0x0C
    DWORD Base;                    // 0x10
    DWORD NumberOfFunctions;       // 0x14
    DWORD NumberOfNames;           // 0x18 => nhìn zô đây
    DWORD AddressOfFunctions;      // 0x1C
    DWORD AddressOfNames;          // 0x20 => senpai notice me
    DWORD AddressOfNameOrdinals;   // 0x24
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
```

Nên code sau khi được chỉnh sửa để hiểu rõ hơn các biến đang làm gì, nhìn chương trình không bị sai sót.


Mình đã thực hiện 2 bước: 
+ Gán lại kiểu cho `rbx_2`: 
```C
struct _IMAGE_EXPORT_DIRECTORY* rbx_2;
```
(cái này có ở code trên rồi)
+ Chỉnh sửa cú pháp để truy cập bằng tên trường thay vì offset thôi (nếu không thì cực kì dễ ngáo + sai): 
```C
1400053cb                    void* rdx_1 = (uint64_t)*(uint32_t*)((uint64_t)rbx_2->AddressOfNames
1400053cb                        + (NumberOfNames << 2) + DllBase) + DllBase;
```

Về bản chất thì logic vẫn giống y chang
Tổng kết lí do đổi tên và kiểu là: 
+ `rbx_2` => `struct _IMAGE_EXPORT_DIRECTORY*`: Gán đúng kiểu làm mình nhìn rõ ràng hơn về offset. 
+ `r10_1` => `DllBase`: Biểu thị rõ ràng base address của DLL (đã giải thích bên trên kĩ hơn).
+ Thay vì dùng offset `0x18` & `0x20` => Dùng thẳng tên trường luôn: `NumberOfNames`, `AddressOfNames`:  dễ đọc, lại còn chuẩn Bill Gates.


Code đã được rename:
```c
...
1400053a8                uint64_t NumberOfNames = (uint64_t)rbx_2->NumberOfNames;
1400053a8                
1400053af                while (NumberOfNames)
1400053af                {
1400053c0                    NumberOfNames = (uint64_t)(NumberOfNames - 1);
1400053cb                    void* rdx_1 = (uint64_t)*(uint32_t*)((uint64_t)rbx_2->AddressOfNames
1400053cb                        + (NumberOfNames << 2) + DllBase) + DllBase;
...
```

Nãy giờ chỉ giải thích rename thôi, giờ lại vào phần chính, chương trình sẽ lặp lại `NumberOfNames` lần và đọc từ trường `AddressOfNames` trong `_IMAGE_EXPORT_DIRECTORY` với tên hàm xuất tại index cho trước. 

### Hàm hash để tạo ra function name:

Lần tiếp theo code, mình phát hiện một `hash` được tính toán cho tên hiện tại, và kết quả của phép toán được so sánh với một `số random` khác. Phần code này có vẻ như đang duyệt qua các exports của các module đã được nạp vào và kiểm tra xem có một TÊN HÀM NHẤT ĐỊNH hay không. Để tránh so sánh chuỗi (có lẽ là vì obfuscation ), một `hash` nữa được sử dụng để kiểm tra.

```c
...
1400053ce                    int32_t r8_1 = 0x9032307c;
1400053d4                    char i = *(uint8_t*)rdx_1;
1400053d7                    void* rdx_2 = (char*)rdx_1 + 1;
1400053d7                    
1400053dc                    if (i)
1400053dc                    {
1400053f6                        do
1400053f6                        {
1400053e6                            r8_1 = ((int32_t)i ^ r8_1) * 0x1000193;
1400053ed                            i = *(uint8_t*)rdx_2;
1400053f0                            rdx_2 += 1;
1400053f6                        } while (i);
1400053f6                        
1400053ff                        if (r8_1 == 0x1b809b6e)
1400053ff                        {
...
```

Để decode tên hàm, ta chỉ cần sử dụng một bảng các tên hàm khả thi và để script tự tìm tên đúng dựa trên hash và seed (nhưng mình đã quên điều quan trọng nhất: mỗi khối mã hash trong binary có thể khởi tạo một seed riêng.  Mặc dù ở đoạn này seed được gán cố định (`0x9032307c`), ở các vị trí khác seed có thể thay đổi, do đó khi viết script tra ngược tên hàm từ hash, cần cung cấp đúng cặp `hash` + `seed`). 

Mình đã tìm hiểu rất nhiều cách băm (hash) nhanh, 1 trong số đó là [FNV1a](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_functio).

Script của mình + đc `Claude` sửa: 


```py
from ctypes import c_uint32
import sys

def hash(value, seed):
    result = seed
    for char in value:
        result = c_uint32((ord(char) ^ result) * 0x1000193).value
    return result

seed = int(sys.argv[2],16)
names = open("names", "r").readlines()
for name in names:
    if hash(name.strip(), seed) == int(sys.argv[1],16):
        print(name)
        break
```

Chỉ khác `FNV1a` ở chỗ seed khởi tạo của mình có thể thay đổi, còn của thuật toán gốc thì phải cố định, còn lại XOR với kí tự hiện tại hay nhân với `FNV prime` thì y chang thuật toán gốc.

```python 
(argv[2]) #điểm khác biệt ở chỗ này
```

Với names là một file đơn giản chứa tất cả các tên hàm, ví dụ:

```bash
abort
abs
acos
asctime
asin
assert
atan
atan2
atexit
atof
atoi
atol
bsearch
calloc
ceil
...
```
Mình ko thể đính kèm file `names` ở đây nhưng mình nghĩ ai chắc cũng sẽ tự tạo được 1 file như vậy cho họ thôi. 


Script này dùng để tra ngược tên hàm từ một giá trị `hash` và một `seed` đã biết. Nó lặp qua danh sách các tên hàm khả nghi mà mình đã chuẩn bị trước là `names` và tính hash cho từng cái theo thuật toán trong binary. Khi tìm được tên có hash khớp với giá trị cần tra => in ra hàm đó. 

Khi chạy script này, nó sẽ tìm ra đúng tên hàm, nếu tên đó có trong bảng `names`

```bash
$ python search_name.py 0x1b809b6e 0x9032307c
fopen
```

Tên hàm được chọn ở đây là `fopen`. Trace tiếp theo code của hàm nãy giờ, code sau đó sẽ lấy địa chỉ hàm và gọi hàm này với đối số `arg2` (trước đó là `users.json`) và `var_486` (cái ở đầu bài đã đc đề cập là `rb`).

```c
...
14000542b                            int64_t rax_7 = ((uint64_t)*(uint32_t*)(
14000542b                                (uint64_t)rbx_2->AddressOfFunctions + DllBase + ((uint64_t)
14000542b                                *(uint16_t*)((uint64_t)rbx_2->AddressOfNameOrdinals
14000542b                                + DllBase + (NumberOfNames << 1)) << 2)) + DllBase)(arg2, 
14000542b                                &var_468);
...
```

### Tổng kết nãy giờ dc gì: 
Bài này có 2 kĩ thuật chính để `obfuscation` là XOR để ẩn chuỗi và `hash` để giấu lời gọi hàm. Ta có thể bắt đầu decode từ bây giờ.

Hàm đầu tiên (`sub_1400052d0`) mà ta đã biết là mở tệp `users.json`. Hàm này thực hiện chuỗi các lệnh như sau (call đã bị obfuscated & mình deobfuscate + take notes như bên dưới):

```C
fp = fopen("users.json", "rb");
fread(buffer, 1, 1024, fp);
// tạo vector<char> từ buffer
fclose(fp);
```

=> Do đó, có thể đặt tên lại hàm này thành `ReadInpFile` hoặc tên nào mà bạn thích.

Quay lại hàm `main`, mọi thứ trở nên rõ ràng hơn rất nhiều...

```C
140005a42        int64_t var_c0 = 0x60b7f02f67e80d38;
140005a58        int64_t var_b8 = 0x66a934904cd0c7c8;
140005a6e        void* const s_3 = 0xfc49a01158d7e4d;
140005a87        int64_t var_58 = 0x66a934904cd0c7a6;
140005a9a        var_c0 ^= s_3;  // kết quả là chuỗi "users.json"
140005aa7        void* s;
140005aa7        int64_t* s_4 = ReadInpFile(&s, &var_c0);
```

+ `var_c0` ban đầu là 1 `số không rõ` nhưng  sau đó được XOR với `s_3`, tạo ra chuỗi `users.json`.
+ Hàm `ReadInpFile` sau đó được gọi với tham số là `buffer s` và tên file vừa giải mã được `users.json`.
+ Đây chính là cách chương trình ẩn chuỗi và không lộ tên file/hàm trong binary.


Việc ta cần làm bây giờ là duyệt qua tất cả các hàm còn lại :skull: và giải mã chúng theo cách tương tự, cho đến khi tìm thấy những đoạn quan trọng giúp chúng ta giải mã lại file được đính kèm `users.json.enc` => Giải được có flag.

### Anti-debugging: 
Sau khi đọc file input, chương trình kiểm tra xem có debugger đang hoạt động kh, và nếu phát hiện , nó sẽ gọi tới một hàm có khả năng thoát chương trình hay làm khó dễ việc debug, cụ thể ở đây là hàm `sub_140002ba0`.

```c
...
140005baf                        if (r8_1 == 0x81a773bc)  // IsDebuggerPresent
140005baf                        {
140005bd2                            if (((uint64_t)*(uint32_t*)(
140005bd2                                    (uint64_t)*(uint32_t*)((char*)rdi_2 + 0x1c) + r10_1 + (
140005bd2                                    (uint64_t)*(uint16_t*)(
140005bd2                                    (uint64_t)*(uint32_t*)((char*)rdi_2 + 0x24) + r10_1
140005bd2                                    + (r9_1 << 1)) << 2)) + r10_1)())
140005bd8                                sub_140002ba0();
...
```

Tóm tắt = ảnh: 
![upload_442f78800b3bda42de271cb4cadc96a4](https://hackmd.io/_uploads/B1dfIgGZxx.png)


![upload_6f3ac994cab3ca239b4631bde0bb37b4](https://hackmd.io/_uploads/By-N8gzbxl.png)

Trông dễ hiểu hơn chưa nhỉ? Về cơ bản thì code là: 

```C
if (IsDebuggerPresent()) {
  sub_140002ba0();
}
```

### Lý do sinh ra BSoD:

Trace tiếp `sub_140002ba0` xem?
![upload_aa7236f08e539b2ce1d2e65670c0afb6](https://hackmd.io/_uploads/HyurUgzbgl.png)

```bash
$ python search_name.py 0xe1f8d3d -0x3385d04c
GetModuleHandleA
```
![upload_b35434d996d4e117a2c2e1296ac8a312 (1)](https://hackmd.io/_uploads/BkFo8lM-xe.png)
![upload_ebe20c3befd2107dace76aff4f362b34 (1)](https://hackmd.io/_uploads/r1N38ezbxg.png)


Đọc trang [này](https://stackoverflow.com/questions/50996439/can-someone-explain-rtladjustprivilege) và xem video demo [này](https://www.youtube.com/watch?v=s27TVF0RWu4) để hiểu nó là cái gì. 
![upload_3f6a2cefa0ac37fd981ff681c5d75988](https://hackmd.io/_uploads/rkQAUgfblg.png)

![upload_ef0b30a3dcd6e2ced468f29dd2e4aabd](https://hackmd.io/_uploads/SJ7JDxzZlg.png)

Research tài liệu [này](http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FError%2FNtRaiseHardError.html).
=> Có thể tóm tắt như sau: 
```
NtRaiseHardError(



  IN NTSTATUS             ErrorStatus,
  IN ULONG                NumberOfParameters,
  IN PUNICODE_STRING      UnicodeStringParameterMask OPTIONAL,
  IN PVOID                *Parameters,
  IN HARDERROR_RESPONSE_OPTION ResponseOption,
  OUT PHARDERROR_RESPONSE Response );
```
```ErrorStatus = 0xc0000420
NumberOfParameter = 0
UnicodeStringParameterMask = 0
Parameters = NULL
Response = &var_90
```

NHƯNG thử tra giá trị `0xc0000420` xem thực sự có nghĩa là gì đi đã
Tra thì ra là `STATUS_ASSERTION_FAILURE`

`Response = &var_90`: & là toán tử lấy địa chỉ. Vì vậy, &var_90 sẽ trả về địa chỉ của vùng nhớ được đặt tên là var_90.
![upload_b7901f651ccb7016e33760c4828901b9](https://hackmd.io/_uploads/SkKePxGbll.png)
=> `ResponseOption` = 6

Research tiếp, tìm được cái này: 
![upload_23457932c3359560a76fbec7272ff864](https://hackmd.io/_uploads/HyjWweGblx.png)
6 => `OptionShutdownSystem` 
Vậy hệ thống sẽ tắt nguồn ngay lập tức

Thế tổng kết nó sẽ là `NtRaiseHardError(STATUS_ASSERTION_FAILURE, 0, 0, NULL, OptionShutdownSystem, &response);`

Vậy là hàm `sub_140002ba0` thực hiện API call tới `NtRaiseHardError` với các tham số sau:

+ `ErrorStatus` = 0xc0000420

+ `NumberOfParameters` = 0

+ `UnicodeStringParameterMask` = 0

+ `Parameters` = 0

+ `ResponseOption` = OptionShutdownSystem

+ `Response` = &response

Lệnh này gây ra lỗi hệ thống khá căng với mã lỗi là `0xc0000420` => gây BSOD. 

### Mã hoá bằng AES CBC (Từ đoạn này sẽ 0 giải thích nhiều nữa, có thể đọc link đính kèm): 
Hàm tiếp theo được gọi là `sub_140001950`. Deobfuscate tương tự những phần trước, deobfuscate strings, deobfuscate mấy cái call tới hàm. Bằng cách này mình đã tìm đc 1 số 'call thú vị' (Đơn giản chỉ cần quăng cho `Cursor Student` để nó lọc những phần không đáng qtam tới => 0 đc đưa vào code, tất cả phần duyệt export table đều có logic giống nhau. Mình quan tâm đến tên hàm được gọi và các tham số mà nó nhận mà thôi).

```c
...
140001a1a        zmm0 = var_160;
140001a1f        zmm0 ^= rax_4;  // advapi32
140001a26        var_160 = zmm0;
...
140001aad                        if (rdx_4 == 0xa6b3508e)  // LoadLibraryA
140001aad                        {
140001ad6                            int64_t rax_10 = ((uint64_t)*(uint32_t*)(
140001ad6                                (uint64_t)*(uint32_t*)((char*)r11_2 + 0x1c) + r10_1 + ((
140001ad6                                uint64_t)*(uint16_t*)(
140001ad6                                (uint64_t)*(uint32_t*)((char*)r11_2 + 0x24) + r10_1
140001ad6                                + (r9_1 << 1)) << 2)) + r10_1)(&var_160);
140001af3                            int64_t var_a0;
140001af3                            __builtin_memcpy(&var_a0, 
140001af3                                "\x0e\x0c\xf4\x65\x75\xdb\xa7\x7e\xd3\xae\xa2\x29\xd3\x5b\xc7\x12\xe2\x6f\x8f\xac\x7f\xb6\x91\xce\x28\xf1\x38\x56\xae\x8c\x0d\x8c\x4d\x7e\x8d\x15\x01\x9a\xc4\x0f\xa6\xc7\xd0\x4c\x90\x34\xa9\x66\x87\x17\xfb\xed\x7f\xb6\x91\xce\x28\xf1\x38\x56\xae\x8c\x0d\x8c", 
140001af3                                0x40);
140001bcf                            int64_t var_80;
140001bcf                            var_a0 ^= var_80; // CryptAcquireContextA
...
140001c5d                                            // GetProcAddress
140001c5d                                            if (rdx_8 == 0xcd948407)
140001c5d                                            {
140001ca5                                                int32_t rax_16 = ((uint64_t)*(uint32_t*)((
140001ca5                                                    uint64_t)*(uint32_t*)((char*)r11_4 + 0x1c)
140001ca5                                                    + r10_2 + ((uint64_t)*(uint16_t*)((
140001ca5                                                    uint64_t)
140001ca5                                                    *(uint32_t*)((char*)r11_4 + 0x24)
140001ca5                                                    + r10_2 + (r9_2 << 1)) << 2)) + r10_2)(
140001ca5                                                    rax_10, &var_a0)(&var_110, 0, 0, 0x18, 
140001ca5                                                    0xf0000000);
... 
```

Phân tích code 1 chút: 
Tự load các hàm crypto từ thư viện `advapi32.dll` bằng cách:
+ Tự resolve `LoadLibraryA` → Load `advapi32.dll`
+ Resolve `GetProcAddress` → Lấy địa chỉ của `CryptAcquireContextA`,...

```
BOOL CryptAcquireContextA(
  HCRYPTPROV *phProv,     // Con trỏ nhận handle (HCRYPTPROV) dùng trong các hàm mã hóa tiếp theo
  LPCSTR     pszContainer, // Tên container để lưu key. Có thể là NULL nếu không cần lưu key lâu dài
  LPCSTR     pszProvider,  // Tên provider, có thể NULL để dùng mặc định
  DWORD      dwProvType,   // Loại provider. Phổ biến nhất là PROV_RSA_AES (24)
  DWORD      dwFlags       // Flag để chỉ cách xử lý: tạo mới container, chỉ truy xuất,...
);
```

+ Gọi các hàm crypto đã lấy được (mở Binja lên đọc thì mình nghĩ mọi người đều biết các hàm đó là gì).

Mình đã để comment đầy đủ trên code. Có thể đọc comment để hiểu

Flow chính:

```c
CryptAcquireContextA(&hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)
CryptImportKey(hCryptProv, KeyData, sizeof(KeyData), NULL, 0, &hCryptKey)
CryptSetKeyParam(hCryptKey, KP_MODE, (const BYTE*)&cipherMode, 0)
CryptSetKeyParam(hCryptKey, KP_IV, IVData, 0)
CryptEncrypt(hCryptKey, NULL, TRUE, 0, Buffer, &BufferLength, BufferLength)
...
```

+ Cái đầu thì note bên trên rồi
+ [CryptImportKey](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptimportkey), [CryptSetKeyParam](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptsetkeyparam), [CryptEncrypt](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptencrypt)
Vì sao `CryptSetKeyParam` được gọi 2 lần á? Vì hàm này được dùng để thiết lập các thông số khác nhau của key mã hóa, và trong trường hợp này, chương trình đang thiết lập hai thông số riêng biệt: đó là `KP_MODE` tức chế độ mã hóa (trong bài này là CBC tức `cipherMode` là 1) và `Vector khởi tạo (IV)` đc dùng trong chế độ như CBC để đảm bảo an toàn (`IVData` là `IV` đã lấy từ `sub_1400016a0` vì sao thì hồi sau sẽ rõ).

#### Ahh crypto's things momment....

Lúc này ta cơ bản biết rằng tệp này được mã hóa bằng `AES CBC`. Vì sao? `AES CBC` là chế độ mã hóa mà trong đó mỗi khối văn bản đã mã hóa phụ thuộc vào khối trước đó thông qua phép XOR (trùng với cách obfuscation + theo flow trên phân tích, việc sử dụng các hàm như `CryptSetKeyParam` để thiết lập IV và các tham số khác, cộng với việc mã hóa sử dụng hàm `CryptEncrypt`, cho thấy chắc chắn nó phải là `AES CBC`). Cơ mà muốn decode thì ta còn thiếu 2 cái cực kì quan trọng, Đó là `khóa` và `vector khởi tạo (IV)`.

Khóa được nhập vào bằng cách gọi hàm `CryptImportKey` và truyền dữ liệu khóa thông qua [PUBLICKEYSTRUC](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-publickeystruc) vào hàm này. Dữ liệu được cung cấp bởi `var_d8`.

Vì sao mình biết `CryptImportKey` đang nhận một `PUBLICKEYSTRUC` (hay đúng hơn là  [PLAINTEXTKEYBLOB](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-publickeystruc)), dù trong mã không hề ghi tên struct.

Xét cấu trúc của `CryptImportKey` trước: 
```
BOOL CryptImportKey(
  HCRYPTPROV  hProv,
  const BYTE *pbData,   // chỗ này chứa key + header nè
  DWORD       dwDataLen,
  HCRYPTKEY   hPubKey,
  DWORD       dwFlags,
  HCRYPTKEY   *phKey
);
```

Trong đó `pbDat` trỏ đến một buffer mà bắt buộc phải bắt đầu bằng một `PUBLICKEYSTRUC`. 

Nếu mà ta truyền key `raw` thì kiểu dữ liệu đc dùng là [PLAINTEXTKEYBLOB](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-publickeystruc).

Xem đoạn này đi: 

```c
...
140001d0b    int32_t var_d8 = 0x208;
140001d17    int32_t var_d4_1 = 0x660e;
140001d21    int32_t var_d0_1 = 0x10;
...
```

+ `var_d8` = 0x208 => 0x08 = `PLAINTEXTKEYBLOB`, 0x02 = `version`: chuẩn với PUBLICKEYSTRUC
+ `var_d4_1` = 0x660e, tức là `AES-128`
+ `var_d0_1` = 0x10, 16 bytes = độ dài key AES-128

Không phải vì biến tên `var_d8` hay `var_d4_1` mà biết, mà vì:
+ Giá trị nó khớp với sách giáo khoa.
+ `Windows Crypto API` bắt buộc `pbData` phải bắt đầu bằng `PUBLICKEYSTRUC` khi gọi `CryptImportKey` (Đọc ở [đây](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptimportkey))

Xét tiếp đoạn trên, ta thấy, `int32_t` là 4 bytes, `x208` ở d8 sẽ chuyển thành `08 02 00 00` để khớp với 4 byte này theo `little endian`
Tương tự thì ta có `0e 66 00 00`, `10 00 00 00`

Chà, có vẻ Binary Ninja đang cố gắng hiển thị buffer với các giá trị:
`\x08\x02\x00\x00\x0e\x66\x00\x00\x10\x00\x00\x00`. Chắc chắn là `BLOBHEADER` vì sao? Tương ứng ta có: 
+ `08`: bType = PLAINTEXTKEYBLOB (0x08)
+ `02`: bVersion = 2
+ `0e 66`: aiKeyAlg = 0x660e = `CALG_AES_128`
+ `10 00 00 00`: 16 bytes key

Do kiểu dữ liệu là `PLAINTEXTKEYBLOB`, sau phần tiêu đề `BLOBHEADER` là 4 byte thể hiện độ dài khóa, và sau đó là một mảng chứa giá trị khóa.

Check stack layout, ta nhận thấy các giá trị này được lưu trữ từ địa chỉ `rbp+0x88` đến `rbp+0x90`, xuống dưới 1 chút thì có một lời gọi hàm `memcpy` sao chép đối số đầu tiên của hàm vào vị trí `rbp+0x94` – ngay sau phần `BLOBHEADER`. Do đó, phần còn lại của dữ liệu tại đây chính là khóa AES.



```c
...
140001dad              // memcpy
140001dad              if (rdx_15 == 0x42a67421)
140001dad                  break;
140001d8c          }
140001d8c          
140001db2          if (!r9_3)
140001db2              goto label_140001db4;
140001d70      }
140001d70      
140001de2      void var_cc; // located rbp+0x94
140001de2      ((uint64_t)*(uint32_t*)((uint64_t)
140001de2          *(uint32_t*)((char*)r11_6 + 0x1c) + r10_3
140001de2          + ((uint64_t)*(uint16_t*)((uint64_t)
140001de2          *(uint32_t*)((char*)r11_6 + 0x24)
140001de2          + r10_3 + (r9_3 << 1)) << 2)) + r10_3)(
140001de2          &var_cc, *(uint64_t*)arg1, 0x10);
...
```

Quay lại hàm `main`, ta thấy lời gọi hàm:
```C
EncryptBuffer(&var_80, &s, &s_3);
```
Đối số đầu tiên `&var_80` thực chất được khởi tạo thông qua một lời gọi hàm trước đó trong main:
```C
sub_1400016a0(&var_80);
```

```c
1400016a0    int64_t* sub_1400016a0(int64_t* arg1)

1400016a0    {
1400016a0        void var_c8;
1400016c5        int64_t rax_1 = __security_cookie ^ &var_c8;
1400016d4        char* r14 = &data_14000a750;
1400016d4        
1400016ef        if (data_14000a768 > 0xf)
1400016ef            r14 = (*(uint64_t*)data_14000a750);
1400016ef        
14000170c        int64_t var_80;
14000170c        __builtin_memcpy(&var_80, 
14000170c            "\xaa\x8f\xfd\x9d\x29\x64\x46\x9c\x64\x20\x6b\xec\x22\xef\x75\x73\x87\x17\xfb\xed\x7f\xb6\x91\xce\x28\xf1\x38\x56\xae\x8c\x0d\x8c\x4d\x7e\x8d\x15\x01\x9a\xc4\x0f\xa6\xc7\xd0\x4c\x90\x34\xa9\x66\x87\x17\xfb\xed\x7f\xb6\x91\xce\x28\xf1\x38\x56\xae\x8c\x0d\x8c", 
14000170c            0x40);
140001791        int64_t var_60;
140001791        var_80 ^= var_60;
...
14000182e                        if (rdx_2 == 0x4223c4f5)  // memcpy
14000182e                        {
140001861                            ((uint64_t)*(uint32_t*)(
140001861                                (uint64_t)*(uint32_t*)((char*)r11_2 + 0x1c) + r10_1 + ((
140001861                                uint64_t)*(uint16_t*)(
140001861                                (uint64_t)*(uint32_t*)((char*)r11_2 + 0x24) + r10_1
140001861                                + (r9_1 << 1)) << 2)) + r10_1)(*(uint64_t*)arg1, &var_80, 
140001861                                0x10);
...
1400018f0                                            if (r8_5 == 0xb05ec763)
1400018f0                                            {
140001926                                                ((uint64_t)*(uint32_t*)((uint64_t)
140001926                                                    *(uint32_t*)((char*)rdi_2 + 0x1c) + rbx_2
140001926                                                    + ((uint64_t)*(uint16_t*)((uint64_t)
140001926                                                    *(uint32_t*)((char*)rdi_2 + 0x24)
140001926                                                    + rbx_2 + ((uint64_t)r9_2 << 1)) << 2))
140001926                                                    + rbx_2)(arg1[1], r14, 0x10);
...
```


Hàm này sao chép hai chuỗi vào mảng được truyền làm tham số cho hàm. Đầu tiên là 16 byte của một mảng byte đã bị XOR obfuscation và sau đó là một số dữ liệu đến từ một biến toàn cục. Mục đầu tiên của mảng là IV, vì vậy chúng ta tiến hành giải mã chuỗi đó và xác định IV là: `e7 f1 70 88 28 fe 82 93 c2 e7 bb a0 b2 db dc 15`

Phần còn thiếu còn lại là `key`, nó được đọc từ phần tử thứ hai của mảng `arg1` trong hàm encrypt. Do đó, dữ liệu này chắc chắn nằm trong một biến toàn cục. Khi kiểm tra các code tham chiếu của `data_14000a750`, ta thấy rằng hàm `sub_1400042d0` đã gán dữ liệu cho biến này.

Đáng chú ý nhất là hàm `sub_1400042d0` lấy ComputerName bằng cách gọi GetComputerName và tính hash `SHA256` cho tên đó.

`Hash` này được so sánh với một giá trị hash đã được mã hóa. Ta có thể dump code và rồi deobfuscate bằng script. 

```C
#include <stdint.h>
#include <string.h>

void foo(uint8_t* output) {
    uint64_t D1[10] = {
        0x0f14bfcb3b5395c0,
        0x7598edafbc6813d2,
        0xa3a8570e569c0b5b,
        0x78f7c6b13432ce03,
        0x3137359115416fee,
        0x25627df6f75efab5,
        0x5cc00aae4386bc25,
        0x8201b67fff8c5912,
        0xc7834c0629c7f55f,
        0xcdca4dd7e40b2e18
    };

    uint64_t D2[10] = {
        0x692387ae5860a7f7,
        0x11a08e9f8a5d26b0,
        0xc7c9316835ac6e69,
        0x4895a5890d03ac32,
        0x015150f273775ddb,
        0x465b4dc39569c8d4,
        0x68f16fcc70b4d91d,
        0xb431831dcfe96a76,
        0xc7834c0629c7f55f,
        0xcdca4dd7e40b2e18
    };

    for (int i = 0; i < 10; i++) {
        uint64_t result = D1[i] ^ D2[i];
        memcpy(output + i * sizeof(uint64_t), &result, sizeof(uint64_t));
    }
}
```

`D1` & `D2` là 2 mảng dữ liệu tĩnh có sẵn, đơn giản chỉ là duyệt qua từng cặp phần tử `D1[i]` và `D2[i]` rồi XOR thôi.

=> Chạy thu được hash này: `723ce87fb5560c8d2e0cffad1b198cb0526fcef0a27b509c8e23be14d3e0b506`

![image](https://hackmd.io/_uploads/ByozZXM-gx.png)

Nếu các giá trị hash không khớp (chương trình được chạy trên một máy có name không khớp với hash này) => shut down

![image](https://hackmd.io/_uploads/HJ6oZQG-lg.png)
Nếu hash giống nhau thì `data_14000a750` sẽ được gán bằng `Computer Name`.

Quay lại hàm `sub_1400016a0` (hàm trả về cả IV và key), chúng ta thấy rằng key được tạo ra từ `Computer Name` và được pad tới độ dài 16 byte bằng cách gán thêm các byte có giá trị `0xa0` vào.

Vì vậy, `key` mà chúng ta tìm được sẽ là: `50 45 54 45 52 a0 a0 a0 a0 a0 a0 a0 a0 a0 a0 a0.`




Với việc đã có đầy đủ `khóa mã hóa` và `vector khởi tạo`, ta hoàn toàn có thể tiến hành giải mã dữ liệu, xài các tools online như `Cyberchef` cũng được, script của mình: 

```py
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

key = b"\x50\x45\x54\x45\x52\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0"
iv = b"\xe7\xf1\x70\x88\x28\xfe\x82\x93\xc2\xe7\xbb\xa0\xb2\xdb\xdc\x15"
ciphertext = open("user.json.enc", "rb").read()

cipher = AES.new(key, AES.MODE_CBC, iv)


try:
    plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
    print(plaintext.decode())
except ValueError as e:
    print("Decryption failed:", str(e))
```


```json
[
    {
      "username": "clowncs",
      "password": "sadboi2k5kekeke",
      "flag": "FLAG{easy_peasy_lemon_squeezy}"
    },
    {
      "username": "robbert1978",
      "password": "shirokoshirkos",
      "flag": "FLAG{nnnnnnnnnnnnnnnnnnnnnnnnn}"
    },
    {
      "username": "Shin24",
      "password": "moimoimoimoimoimoimoi",
      "flag": "FLAG{etouuuuuuuuuuuuuuuuuuuuuu}"
    },
    {
      "username": "tr4c3d4tr4il",
      "password": "toiiukembanh!!!!!!!!!!!!!!!",
      "flag": "FLAG{toiiukembanh!!!!!!!!!!!!!}"
    },
    {
      "username": "d0qbu",
      "password": "gaugaugaugaguaguagaugau",
      "flag": "FLAG{tui_la_chon_cua_doi}"
    },
    {
      "username": "Yuu",
      "password": "alimegirl:333333",
      "flag": "FLAG{toi_la_boi_anime}"
    },
    {
      "username": "Jalink",
      "password": "36quetoi",
      "flag": "FLAG{ecujalinkknekkkkkk}"
    },
    {
      "username": "tvdat2004",
      "password": "cryptoooooooooooooooooo",
      "flag": "FLAG{cryptoooooooooooooooooo_kekekekekeke}"
    },
    {
      "username": "Giapppppppp",
      "password": "rooooooooooooooooooooooytooci",
      "flag": "FLAG{gehkekekkekekebehehhehehehehheheheh}"
    },
    {
      "username": "W1",
      "password": "W1_flag",
      "flag": "W1{simple_technique_but_it_works_well_b4915726}"
    }
]
  ```




Trong quá trình giải bài này, anh little timmy đã cho mình rất nhiều gợi ý đáng dùng, mình đã follow từ câu đầu tiên mà anh ấy nhắn mà mình trích bên trên, xin cảm ơn anh rất nhiều!
![image](https://hackmd.io/_uploads/SkI8qWfWle.png)









