Anya Gacha

分析

打開文件夾,發現是Unity遊戲

Untitled

  • 先打開遊戲看看是在干什麼
  • 大概是一個抽卡遊戲,左上是金錢( 每次-10 ),按左下按鈕開始抽取

Untitled

  • 逆Unity遊戲通常可直接找到它的Assembly-CSharp.dll文件( 在\AnyaGacha_Data\Managed目錄下 ),將其拉入dnspy進行分析( 32位 )
  • 在開始時對一些數據進行了初始化,這裡其中一個數據用了隨機數

Untitled

  • 找到按下【抽卡】按鈕所調用的函數
  • 這裡首先判斷【金錢】是否>10,若是才能繼續
  • 後面再對一些數據進行處理,然後調用this.Upload()

Untitled

  • 進入this.Upload(),看到它是通過發送網路請求來驗證
  • 但我看不懂要怎樣才能走到this.succeed(text),而上面有提到一開始使用了隨機數來初始化數據,因此我猜測這可能與真的抽卡遊戲一樣,是概率問題
  • 所以我的破解思路:不斷調用this.Upload(),直到成功為至

Untitled

暴力破解

在dnspy中,右鍵Edit Method,修改wish函數

1
2
3
4
5
6
7
8
9
public void wish()
{
int num = this.unmask_value(this.value);
//...刪了一些代碼
this.value = this.mask_value(num);
this.counter = this.mySHA256.ComputeHash(this.counter);
this.loading.SetActive(true);
base.StartCoroutine(this.Upload());
}

同樣方法修改Upload函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private IEnumerator Upload()
{
WWWForm wwwform = new WWWForm();
string str = Convert.ToBase64String(this.counter);
wwwform.AddField("data", str);
UnityWebRequest www = UnityWebRequest.Post(this.server, wwwform);
Debug.Log("Posted: " + str);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
this.wish(); //修改的地方
Debug.Log(www.error);
}
else
{
this.loading.SetActive(false);
string text = www.downloadHandler.text;
if (text == "")
{
this.wish(); //修改的地方
this.fail();
}
else
{
this.succeed(text);
}
}
yield break;
}

backfrom這是按【返回鍵】後會調用的函數

1
2
3
4
5
6
public void backfrom(GameObject g)
{
g.SetActive(false);
this.mainpage.SetActive(true);
this.wish(); //修改的地方
}
  • 然後保存,方法:File→Save Module
  • 之後打開遊戲,按【make a wish】之後,狂按右上的【back】
  • 理論上按下【make a wish】之後,慢慢等就會自動出flag,但不知為何出不了( 等得不夠久? ),但只要按上述那樣改掉backfrom函數,然後狂按【back】就很快會出flag

Untitled

Game

分析

題目描述如下圖所示:

Untitled

  • 先拉入IDA,去main函數看看
  • 看不出什麼,再繼續進入level_gen()

Untitled

  • 要求用戶輸入1、2、3,分別對應左、中、右三條路
  • 然後之後會調用level_next,繼續跟入去

Untitled

  • 可以看出共有3個結果,分別是【繼續3選1】【到達某個出口】【到達需要password的地方】
  • 在需要輸入password的地方輸入password後,會與fnv_1a_32函數的返回值進行對比,若一致就會輸出某些東西,推測與flag有關

Untitled

解題過程

記錄一下當時在做題時的一些經歷

  1. 看到這樣類似走迷宮的題,本來打算先將【地圖】dump出來再分析,但試著試著突然想起題目給了個nc rev.chal.csaw.io 5003,代表他將數據放在了服務器上,若我在本地上直接動調會失去【地圖】數據( 然後我就放棄了 )
  2. 不知道如何將服務器上的數據dump下來,之後我想了一想,發現以我的水平,好像只能嘗試將【地圖】手動爆破出來

手動爆破的地圖如下圖所示:

  1. 最底下是超始位置,左、中、右3格對應1、2、3
  2. 黃圈P代表要輸入密碼的地方( 上方有橙色勾的是不重複的地方,其他沒有橙色勾的黃圈P會與其中一個有橙色勾的地方重複 )
  3. 綠色w是出口( 沒有什麼用 )
  4. 普通白色圈代表繼續【3選1】
  5. 黑色x代表死路

Untitled

  • 現在要先找出所有密碼。去到驗證密碼的地方分析
  • v11的大小是50字節,但qmemcpy卻只複製了”cook”4個字節,明顯有古怪
  • 雙擊”cook”,發現其下還有東西,且正好可以每10個字節1組,分成5組
  • 而由fnv_1a_32(&v11[10 * v8]) == pass也可以看出fnv_1a_32函數也是每10個字節1組進行處理

Untitled

Untitled

編寫腳本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
#include <Windows.h>
using namespace std;

__int64 __fastcall fnv_1a_32(unsigned __int8* a1)
{
__int64 result; // rax
int v2; // edx

for (result = 2166136261LL; ; result = 16777619 * (v2 ^ (unsigned int)result))
{
v2 = *a1;
if (!(BYTE)v2)
break;
++a1;
}
return result;
}

int main() {
unsigned char aCook[] =
{
0x63, 0x6F, 0x6F, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x66, 0x6C, 0x61, 0x77, 0x65, 0x64, 0x00, 0x00, 0x00, 0x00,
0x67, 0x72, 0x61, 0x76, 0x65, 0x6C, 0x00, 0x00, 0x00, 0x00,
0x6B, 0x69, 0x6E, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x64, 0x65, 0x63, 0x69, 0x73, 0x69, 0x76, 0x65, 0x00, 0x00
};

for (int i = 0; i < 50; i += 10) {
cout << (fnv_1a_32(&aCook[i])) << endl;
}
}
// 結果:
/*
4013828393
1118844294
3000956154
3658736598
1688072995
*/

由於不知道哪個密碼對應哪條路,因此只能逐一嘗試,最終經過無數次的嘗試得出

  1. 11這條路徑的密碼是4013828393,輸出結果為:flag{e@5+er_
  2. 1313這條路徑的密碼是3000956154,輸出結果為:p@yw@115_i5_
  3. 131221這條路徑的密碼是4013828393,輸出結果為:+he_dum6e5+_
  4. 1312221這條路徑的密碼是1688072995,輸出結果為:ide@_ever!!}
  5. 222這條路徑的密碼是1118844294,輸出結果為:e995_6ehind_

將路徑由小到大排列得出flag為:flag{e@5+er_e995_6ehind_p@yw@115_i5_+he_dum6e5+_ide@_ever!!}