终于放寒假了……重操旧业,写点博客。

记录一款一直有在追踪的Unity游戏,它发布公开的windows版和赞助特供的安卓版,为了防止一人赞助到处分享,安卓版在进入主界面之前就会要求登录认证,windows版在进入画廊时要求认证。可以通过Patreon提供的接口在线验证,也可以采取半离线验证的方式,将一串序列号发送给Discord的机器人,机器人返回验证码,使用验证码登录。

最开始它用Mono架构,主要的函数都存储在Assembly-CSharp.dll中,使用dnspy可以看到几乎完全一致的源码,修改也很方便;几个版本之后进行了一些混淆,但是函数逻辑没有大改,审计一下还是可以明白——主要是那时候没有ChatGPT老师和Gemini老师,单单把函数名和类名混淆就能够难倒我了。最后它终于用上了il2cpp,源码全都没有了,只好看汇编代码,慢慢啃吧。Gemini老师救我!

il2cpp时代也一直有一些破解版流出,多半是直接跳过登录界面,并设置最终的返回值为1(True)。这种修改程序控制流的方法很容易被针对,可能引发闪退等,因此还需要将验证时涉及的所有函数检查一遍,绕过所有闪退的分支。

笔者能力不太足,不太会分析完整流程,全靠Gemini和IDA mcp。动控制流在闪退和卡死之间反复横跳,就是进不去,于是不如另辟蹊径,看看这个“半”离线的验证有多离线。为了不读ARM的汇编,就先拿windows版试试水,我实在不想看到各种奇怪的TBNZ了。

Gemini说这个方法全程没有联网通信,让我看看。

老师我不想再看到sub_xxx了

第一步当然是用global-metadata.dat还原一些函数符号和字符串,由于il2cpp将C#代码转换成了native的C++,但是C#的特性不能丢失,因此需要保留符号,大部分函数都可以用metadata还原,还原之后的结果很鲜明,名字很清晰的是库函数,乱糟糟的是游戏的函数,这也能减轻一些分析工作量。

感谢我们伟大的Il2CppDumper…它导出了四个文件和一个文件夹,分别是

  • il2cpp.h:头文件,包含程序中的结构体、共同体等数据结构。例如下面这个不知名结构体()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    struct MethodInfo_12D1B80 {
    Il2CppMethodPointer methodPointer;
    Il2CppMethodPointer virtualMethodPointer;
    InvokerMethod invoker_method;
    const char* name;
    System_Collections_Generic_List_T__c *klass;
    const Il2CppType *return_type;
    const Il2CppType** parameters;
    const Il2CppRGCTXData* rgctx_data;
    union
    {
    const void* genericMethod;
    const void* genericContainerHandle;
    };
    uint32_t token;
    uint16_t flags;
    uint16_t iflags;
    uint16_t slot;
    uint8_t parameters_count;
    uint8_t bitflags;
    };
  • script.json:函数签名与地址的对应关系,虽然看起来还是有很多奇怪的地方,比如下划线之类的。具体的细节等到IDA中再看。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "Address": 8404496,
    "Name": "StoryCharacterAppearance.KJBNJFJGOBF$$LFABPAFPJML",
    "Signature": "UnityEngine_Sprite_o* StoryCharacterAppearance_KJBNJFJGOBF__LFABPAFPJML (StoryCharacterAppearance_KJBNJFJGOBF_o* __this, int32_t FJOKBEOHOMP, System_String_o* GBAGOEMADCD, const MethodInfo* method);",
    "TypeSignature": "iiiii"
    },
    {
    "Address": 8398384,
    "Name": "StoryCharacterAppearance.KJBNJFJGOBF$$HAABBCKKNNE",
    "Signature": "System_ValueTuple_int__Sprite__o StoryCharacterAppearance_KJBNJFJGOBF__HAABBCKKNNE (StoryCharacterAppearance_KJBNJFJGOBF_o* __this, System_String_o* PAENHOCOOGG, System_String_o* GBAGOEMADCD, const MethodInfo* method);",
    "TypeSignature": "iiiii"
    },
    {
    "Address": 8407424,
    "Name": "StoryCharacterAppearance.KJBNJFJGOBF$$NJONCPKBFPP",
    "Signature": "UnityEngine_Sprite_o* StoryCharacterAppearance_KJBNJFJGOBF__NJONCPKBFPP (StoryCharacterAppearance_KJBNJFJGOBF_o* __this, int32_t FJOKBEOHOMP, System_String_o* GBAGOEMADCD, const MethodInfo* method);",
    "TypeSignature": "iiiii"
    },
    ...
  • stringliteral.json:字符串,这些字符串和metadata中的并不相同,更像是库函数会用到的字符串,总之也导出了。

    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
    {
    "value": "Test Error from Thread",
    "address": "0x2D9DFC0"
    },
    {
    "value": "CharIndex",
    "address": "0x2D9DFE8"
    },
    {
    "value": "align-self",
    "address": "0x2D9DFF0"
    },
    {
    "value": "IsLatin-1Supplement",
    "address": "0x2D9E020"
    },
    {
    "value": "size must be \u003C= buffer.Length - offset",
    "address": "0x2D9E028"
    },
    {
    "value": " standalone=",
    "address": "0x2D9E050"
    },
    ...
  • dump.cs:恢复出的大致C#代码,代码中没有函数体,因此还是只能去看汇编。

    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
    // Namespace: Gamexxx.View
    public class LoginView : ViewItem // TypeDefIndex: 2206
    {
    // Fields
    private Patreon NDODOCMEOPD; // 0x48
    private Action MMJAKNHAJAN; // 0x50
    private Text PPMEGACKABO; // 0x58
    private InputField LHNNLPOPGNG; // 0x60
    private GameObject ELCLHBECKLJ; // 0x68
    private GameObject CIFEJIHJJOF; // 0x70
    private string OPDJLGGICOL; // 0x78
    [ConversationPopup(False, True)]
    private string MOEDNKCPNEO; // 0x80
    private CanvasGroup DAKKHAJGMNI; // 0x88
    private GameObject MOCMFKLDAAH; // 0x90

    // Methods

    // RVA: 0x690CB0 Offset: 0x6900B0 VA: 0x180690CB0
    private void DPKPEDADGNP() { }

    // RVA: 0x6925A0 Offset: 0x6919A0 VA: 0x1806925A0
    private void IPHBDJPAOAA(string NMIMHINJJHA) { }

    // RVA: 0x694180 Offset: 0x693580 VA: 0x180694180
    public void OFGJPKPADJH(Transform DICMALINFFD) { }

    // RVA: 0x692740 Offset: 0x691B40 VA: 0x180692740
    private void JFLGMGPBJEI() { }

    // RVA: 0x5ED1B0 Offset: 0x5EC5B0 VA: 0x1805ED1B0
    private void JJJBJAHFFLO() { }

    ...
    }
  • DummyDll:将大的GameAssembly.dll拆解成Mono架构的小dll,这样我们可以只专注于Assembly-CSharp.dll,不用管其他的库函数。当然,这些小dll用dnspy打开看也没有函数体。

快学啊IDA

Il2CppDumper附带一些ida_py3.py之类的脚本,可以将提取出的信息接入IDA中。

(十万个sub_xxx谁敢看)

noscript

选中File->Script file…,再选中ida_with_struct_py3.py,将刚生成的script.json和il2cpp.h选上,稍等片刻,我们就有了一些写着Unity的方法,这些名字不明不白的是混淆后的类和函数。

scriptdone

我们的目标是:Discord验证!

随便看看没发现什么,Shift+F12也没搜到这些字符串,看来另有玄机。经过测试,直接点击log in会提示Empty String,随便输入code会提示This code is incorrect,直接全文搜索一下这俩,Ctrl+F大法好。

login

string

搜到了一个Init,看起来是测bug用的?不管,总之是看到奇怪的StringLiteral_11511,点进去发现这也不是具体的字符串,只是一个数值,但是注释里有这个字符串。咨询了Gemini知道它对应的字符串存储在metadata中,如果要修改,应该去找metadata。

string2

按X查一个交叉引用。同样的方法搜索一下Empty string。

string3

string4

同时出现在这两个字符串的xref的只有MAPMGCMPMAM$$MDMJCNKJPBD,我们先看这个函数。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
il2cpp:0000000180805F30 ; bool MAPMGCMPMAM__MDMJCNKJPBD(System_String_o *HBLNEJNOOBL, const MethodInfo *method)
il2cpp:0000000180805F30 MAPMGCMPMAM$$MDMJCNKJPBD proc near ; CODE XREF: Gamexxx_View_LoginView$$DIICNKOGNAJ+2A↑p
il2cpp:0000000180805F30 ; Gamexxx_View_LoginView$$DOFKAGPLKEH+2A↑p ...
il2cpp:0000000180805F30 push rbx
il2cpp:0000000180805F32 sub rsp, 20h
il2cpp:0000000180805F36 cmp cs:byte_182F66C0C, 0
il2cpp:0000000180805F3D mov rbx, rcx
il2cpp:0000000180805F40 jnz short loc_180805F85
il2cpp:0000000180805F42 lea rcx, Method$EGJHFIOEIAD_IIPIJJMJOLF_OJFCJJBIDAK___ ; Method$EGJHFIOEIAD.IIPIJJMJOLF<OJFCJJBIDAK>()
il2cpp:0000000180805F49 call sub_180290560
il2cpp:0000000180805F4E lea rcx, EGJHFIOEIAD_TypeInfo ; EGJHFIOEIAD_TypeInfo
il2cpp:0000000180805F55 call sub_180290560
il2cpp:0000000180805F5A lea rcx, Singleton_ViewManager__TypeInfo ; Singleton<ViewManager>_TypeInfo
il2cpp:0000000180805F61 call sub_180290560
il2cpp:0000000180805F66 lea rcx, StringLiteral_13071 ; Empty String.
il2cpp:0000000180805F6D call sub_180290560
il2cpp:0000000180805F72 lea rcx, StringLiteral_11511 ; This code is incorrect, please check and try again.
il2cpp:0000000180805F79 call sub_180290560
il2cpp:0000000180805F7E mov cs:byte_182F66C0C, 1
il2cpp:0000000180805F85
il2cpp:0000000180805F85 loc_180805F85: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+10↑j
il2cpp:0000000180805F85 test rbx, rbx
il2cpp:0000000180805F88 jz loc_1808060B9
il2cpp:0000000180805F8E cmp dword ptr [rbx+10h], 0
il2cpp:0000000180805F92 jz loc_180806089
il2cpp:0000000180805F98 movzx ecx, cs:byte_182F66BFE
il2cpp:0000000180805F9F test cl, cl
il2cpp:0000000180805FA1 jnz short loc_180805FB7
il2cpp:0000000180805FA3 lea rcx, MAPMGCMPMAM_TypeInfo ; MAPMGCMPMAM_TypeInfo
il2cpp:0000000180805FAA call sub_180290560
il2cpp:0000000180805FAF mov cl, 1
il2cpp:0000000180805FB1 mov cs:byte_182F66BFE, cl
il2cpp:0000000180805FB7
il2cpp:0000000180805FB7 loc_180805FB7: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+71↑j
il2cpp:0000000180805FB7 mov rdx, cs:MAPMGCMPMAM_TypeInfo ; MAPMGCMPMAM_TypeInfo
il2cpp:0000000180805FBE mov rax, [rdx+0B8h]
il2cpp:0000000180805FC5 mov r8, [rax]
il2cpp:0000000180805FC8 test r8, r8
il2cpp:0000000180805FCB jz loc_180806081
il2cpp:0000000180805FD1 cmp qword ptr [r8+10h], 0
il2cpp:0000000180805FD6 jz loc_180806081
il2cpp:0000000180805FDC test cl, cl
il2cpp:0000000180805FDE jnz short loc_180805FFA
il2cpp:0000000180805FE0 lea rcx, MAPMGCMPMAM_TypeInfo ; MAPMGCMPMAM_TypeInfo
il2cpp:0000000180805FE7 call sub_180290560
il2cpp:0000000180805FEC mov rdx, cs:MAPMGCMPMAM_TypeInfo ; MAPMGCMPMAM_TypeInfo
il2cpp:0000000180805FF3 mov cs:byte_182F66BFE, 1
il2cpp:0000000180805FFA
il2cpp:0000000180805FFA loc_180805FFA: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+AE↑j
il2cpp:0000000180805FFA mov rax, [rdx+0B8h]
il2cpp:0000000180806001 mov rcx, [rax]
il2cpp:0000000180806004 test rcx, rcx
il2cpp:0000000180806007 jz loc_1808060B9
il2cpp:000000018080600D mov rcx, [rcx+10h] ; this
il2cpp:0000000180806011 test rcx, rcx
il2cpp:0000000180806014 jz loc_1808060B9
il2cpp:000000018080601A xor r8d, r8d ; method
il2cpp:000000018080601D mov rdx, rbx ; HBLNEJNOOBL
il2cpp:0000000180806020 call FJFKHBFABDJ$$MDMJCNKJPBD
il2cpp:0000000180806025 test al, al
il2cpp:0000000180806027 jz short loc_180806059
il2cpp:0000000180806029 xor ecx, ecx ; method
il2cpp:000000018080602B call MAPMGCMPMAM$$HGJLFCJDLHJ
il2cpp:0000000180806030 ; ---------------------------------------------------------------------------
il2cpp:0000000180806030 mov rcx, cs:EGJHFIOEIAD_TypeInfo ; EGJHFIOEIAD_TypeInfo
il2cpp:0000000180806037 cmp dword ptr [rcx+0E4h], 0
il2cpp:000000018080603E jnz short loc_180806045
il2cpp:0000000180806040 call il2cpp_runtime_class_init
il2cpp:0000000180806045
il2cpp:0000000180806045 loc_180806045: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+10E↑j
il2cpp:0000000180806045 mov rcx, cs:Method$EGJHFIOEIAD_IIPIJJMJOLF_OJFCJJBIDAK___ ; method
il2cpp:000000018080604C call EGJHFIOEIAD$$IIPIJJMJOLF_object__6452636656
il2cpp:0000000180806051 mov al, 1
il2cpp:0000000180806053 add rsp, 20h
il2cpp:0000000180806057 pop rbx
il2cpp:0000000180806058 retn
il2cpp:0000000180806059 ; ---------------------------------------------------------------------------
il2cpp:0000000180806059
il2cpp:0000000180806059 loc_180806059: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+F7↑j
il2cpp:0000000180806059 mov rax, cs:Singleton_ViewManager__TypeInfo ; Singleton<ViewManager>_TypeInfo
il2cpp:0000000180806060 mov rcx, [rax+0B8h]
il2cpp:0000000180806067 mov rcx, [rcx] ; this
il2cpp:000000018080606A test rcx, rcx
il2cpp:000000018080606D jz short loc_1808060B9
il2cpp:000000018080606F mov rdx, cs:StringLiteral_11511 ; OIBNBEKDHJK
il2cpp:0000000180806076 xor r9d, r9d ; method
il2cpp:0000000180806079 xor r8d, r8d ; NOFFKECEGCF
il2cpp:000000018080607C call ViewManager$$ShowFadeOutTip
il2cpp:0000000180806081
il2cpp:0000000180806081 loc_180806081: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+9B↑j
il2cpp:0000000180806081 ; MAPMGCMPMAM$$MDMJCNKJPBD+A6↑j
il2cpp:0000000180806081 xor al, al
il2cpp:0000000180806083 add rsp, 20h
il2cpp:0000000180806087 pop rbx
il2cpp:0000000180806088 retn
il2cpp:0000000180806089 ; ---------------------------------------------------------------------------
il2cpp:0000000180806089
il2cpp:0000000180806089 loc_180806089: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+62↑j
il2cpp:0000000180806089 mov rax, cs:Singleton_ViewManager__TypeInfo ; Singleton<ViewManager>_TypeInfo
il2cpp:0000000180806090 mov rcx, [rax+0B8h]
il2cpp:0000000180806097 mov rcx, [rcx] ; this
il2cpp:000000018080609A test rcx, rcx
il2cpp:000000018080609D jz short loc_1808060B9
il2cpp:000000018080609F mov rdx, cs:StringLiteral_13071 ; OIBNBEKDHJK
il2cpp:00000001808060A6 xor r9d, r9d ; method
il2cpp:00000001808060A9 xor r8d, r8d ; NOFFKECEGCF
il2cpp:00000001808060AC call ViewManager$$ShowFadeOutTip
il2cpp:00000001808060B1 xor al, al
il2cpp:00000001808060B3 add rsp, 20h
il2cpp:00000001808060B7 pop rbx
il2cpp:00000001808060B8 retn
il2cpp:00000001808060B9 ; ---------------------------------------------------------------------------
il2cpp:00000001808060B9
il2cpp:00000001808060B9 loc_1808060B9: ; CODE XREF: MAPMGCMPMAM$$MDMJCNKJPBD+58↑j
il2cpp:00000001808060B9 ; MAPMGCMPMAM$$MDMJCNKJPBD+D7↑j ...
il2cpp:00000001808060B9 call sub_180290800
il2cpp:00000001808060B9 ; ---------------------------------------------------------------------------
il2cpp:00000001808060BE db 0CCh
il2cpp:00000001808060BF algn_1808060BF: ; DATA XREF: .pdata:0000000183295ACC↓o
il2cpp:00000001808060BF align 20h
il2cpp:00000001808060BF MAPMGCMPMAM$$MDMJCNKJPBD endp

由于metadata的信息都在注释里,汇编实际上比F5反编译包含了更多的信息,或者实在不行让AI帮忙也可。在注释中看到了LoginView,这个函数多半与登录有关联了。

开头一段不断重复lea xxxcall sub_180290560,推测是在绑定具体的值,获取之后就设置cs:byte_182F66C0C为1,再次调用该函数不必重复获取。test rbx, rbx判断字符串指针*HBLNEJNOOBL是否为空,cmp dword ptr [rbx+10h], 0比较字符串长度是否为0。指针为空属于异常,将进入sub_180290800,这不在我们的分析范围,因此我们从字符串长度为0开始。

字符串长度为0

这部分相对简单,进入loc_180806089后最终call了ViewManager$$ShowFadeOutTip,参数用到了cs:StringLiteral_13071,也就是 Empty String. ,可以猜测这就是弹出通知的函数。

正常比对

还是把F5对照放出来看比较好()不然我看不懂,全是Gemini在看。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
bool MAPMGCMPMAM__MDMJCNKJPBD(System_String_o *HBLNEJNOOBL, const MethodInfo *method)
{
System_String_o *v2; // rbx
__int64 v3; // rdx
__int64 v4; // rdx
__int64 v5; // rdx
__int64 v6; // rdx
char v7; // cl
struct HFKOMCEPCAL_o *NLMHIDLAECP; // r8

v2 = HBLNEJNOOBL;
if ( !byte_182F66C0C )
{
sub_180290560(&Method_EGJHFIOEIAD_IIPIJJMJOLF_OJFCJJBIDAK___, method);
sub_180290560(&EGJHFIOEIAD_TypeInfo, v3);
sub_180290560(&Singleton_ViewManager__TypeInfo, v4);
sub_180290560(&StringLiteral_13071, v5);
sub_180290560(&StringLiteral_11511, v6);
byte_182F66C0C = 1;
}
if ( !v2 )
goto LABEL_20;
if ( v2->fields._stringLength )
{
v7 = byte_182F66BFE;
if ( !byte_182F66BFE )
{
sub_180290560(&MAPMGCMPMAM_TypeInfo, method);
v7 = 1;
byte_182F66BFE = 1;
}
method = (const MethodInfo *)MAPMGCMPMAM_TypeInfo;
NLMHIDLAECP = MAPMGCMPMAM_TypeInfo->static_fields->NLMHIDLAECP;
if ( !NLMHIDLAECP || !NLMHIDLAECP->fields.loginSave )
return 0;
if ( !v7 )
{
sub_180290560(&MAPMGCMPMAM_TypeInfo, MAPMGCMPMAM_TypeInfo);
method = (const MethodInfo *)MAPMGCMPMAM_TypeInfo;
byte_182F66BFE = 1;
}
HBLNEJNOOBL = *(System_String_o **)method[2].virtualMethodPointer;
if ( HBLNEJNOOBL )
{
HBLNEJNOOBL = (System_String_o *)HBLNEJNOOBL->fields;
if ( HBLNEJNOOBL )
{
if ( FJFKHBFABDJ__MDMJCNKJPBD((FJFKHBFABDJ_o *)HBLNEJNOOBL, v2, 0) )
MAPMGCMPMAM__HGJLFCJDLHJ(0);
HBLNEJNOOBL = (System_String_o *)Singleton_ViewManager__TypeInfo->static_fields->Instance;
if ( HBLNEJNOOBL )
{
ViewManager__ShowFadeOutTip((ViewManager_o *)HBLNEJNOOBL, (System_String_o *)StringLiteral_11511, 0, 0);
return 0;
}
}
}
LABEL_20:
sub_180290800(HBLNEJNOOBL, method);
}
HBLNEJNOOBL = (System_String_o *)Singleton_ViewManager__TypeInfo->static_fields->Instance;
if ( !HBLNEJNOOBL )
goto LABEL_20;
ViewManager__ShowFadeOutTip((ViewManager_o *)HBLNEJNOOBL, StringLiteral_13071, 0, 0);
return 0;
}

前两个跳转都不满足的话,加载MAPMGCMPMAM_TypeInfo并设置cs:byte_182F66BFE = 1表示已绑定。接下来的loc_180805FB7对照F5,是NLMHIDLAECP = MAPMGCMPMAM_TypeInfo->static_fields->NLMHIDLAECP;这一行(我也不知道为什么寄存器r8变成了这么大一串东西……),两个jz跳转如果有满足的就会跳转到loc_180806081,直接退出,因此这对应if ( !NLMHIDLAECP || !NLMHIDLAECP->fields.loginSave ) return 0;。接下来又有一次绑定MAPMGCMPMAM_TypeInfo的过程,不太懂,不过也不重要。

loc_180805FFA部分,第一、二个验证call到了loc_1808060B9,和字符串指针为空的结果一样,略过。接下来调用了FJFKHBFABDJ$$MDMJCNKJPBD,根据这个函数的返回值决定跳转到loc_180806059还是调用MAPMGCMPMAM$$HGJLFCJDLHJ。前者的参数有cs:StringLiteral_11511,也就是 This code is incorrect, please check and try again. ,那么后者应该才是验证成功,这下更应该看FJFKHBFABDJ$$MDMJCNKJPBD了。

验证方法

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
il2cpp:00000001806FDFE0 ; bool FJFKHBFABDJ__MDMJCNKJPBD(FJFKHBFABDJ_o *this, System_String_o *HBLNEJNOOBL, const MethodInfo *method)
il2cpp:00000001806FDFE0 FJFKHBFABDJ$$MDMJCNKJPBD proc near ; CODE XREF: FJFKHBFABDJ$$AMKCOMHIBKJ+19E↑p
il2cpp:00000001806FDFE0 ; MAPMGCMPMAM$$MDMJCNKJPBD+F0↓p
il2cpp:00000001806FDFE0 ; DATA XREF: ...
il2cpp:00000001806FDFE0
il2cpp:00000001806FDFE0 arg_0 = qword ptr 8
il2cpp:00000001806FDFE0 arg_8 = qword ptr 10h
il2cpp:00000001806FDFE0 arg_10 = qword ptr 18h
il2cpp:00000001806FDFE0
il2cpp:00000001806FDFE0 mov [rsp+arg_8], rbx
il2cpp:00000001806FDFE5 mov [rsp+arg_10], rbp
il2cpp:00000001806FDFEA push rsi
il2cpp:00000001806FDFEB sub rsp, 20h
il2cpp:00000001806FDFEF cmp cs:byte_182F66094, 0
il2cpp:00000001806FDFF6 mov rbp, rdx
il2cpp:00000001806FDFF9 mov rsi, rcx
il2cpp:00000001806FDFFC jnz short loc_1806FE065
il2cpp:00000001806FDFFE lea rcx, UnityEngine_Application_TypeInfo ; UnityEngine.Application_TypeInfo
il2cpp:00000001806FE005 call sub_180290560
il2cpp:00000001806FE00A lea rcx, Method$Newtonsoft_Json_Linq_Extensions_Value_bool___ ; Method$Newtonsoft.Json.Linq.Extensions.Value<bool>()
il2cpp:00000001806FE011 call sub_180290560
il2cpp:00000001806FE016 lea rcx, StringLiteral_14551 ; <RSAKeyValue><Modulus>kqb7YKFSoVfwpjfZozOf+/CBWZr52i6vGxz2qiGh+jyB5I0fwlSK9hLIFdxfJCgnbwnX6G7Eaj1XZ7zXDWg7iII7FEiwHMNmosKgpMsfnoFF8ow4T4+pzmvYJS0DSOYNZyTzl0wnoD0Sf2FafvtlltNv8tNZGSo/3m9gTF7h1Z6gfJPjnwYJWFkYNbp8EMKhgrju5XElJqZILHfbb4kOQx9jIf745YLsxj8nlI7Fe4n+fN2m5OtweTj45zgZx3sFkmPZ1BGP4kxQScBXUty6scioUskMZDZLvc210x32ZELoLaj3r1eznvurfJt81RAEttCQ7u1E1j8p+JmCo81MjQ==</Modulus><Exponent>AQAB</Exponent><P>zoaxu3zYHGWQaMvXXgFVGINbf5sFFrfJP104euD7TRQowhgV2kjgrPA65je5vn6e18YQ/SpCgoxFRF80Fohwh0vDAaiszdZjEkZLfOzNbmfKrnrpmPTzUkAlM9FCKqzFY/RMs/rV0jAnA19LvMu1lsXKiVC1vXzYwdg/vecUIAk=</P><Q>tch/CSr6UNPfl2SPHEFcei/ORCrG7YiF49UVb6wpOdxpGHXw/NJjStwqT8Cd5heTSA+BS4JmFh2TtagZdGmQ6VqhbnreHRDsEJxLKph9JrM6G7LVXvAGcRFi8ymA10ev3/53SnKsmsALs0w2ruTk3v6FjawUliart0qljhq/oWU=</Q><DP>cOknBm/s3ymP2BuJtXVZBrvaLFueXptARYo7tMKH4c4hsmvklqC9ZQ85xF+3BzTGtwUsiJywHBuASy8ZlTAnBXEgPinvv+Fz7KvN3ZDBh5jrMJU2XU+eL9ut+zRuzlIC4PDRdpyuhemZirhw+dkNgE7GumgL1HsLLn+B6dsKH7E=</DP><DQ>nZAUdHjZENgktI+H7+DDbHElYzaEyHyOOOWIOAxMi4d9XKNV533gbJGa5xe4hSioE0zFMBLts3udfVYg
il2cpp:00000001806FE01D call sub_180290560
il2cpp:00000001806FE022 lea rcx, StringLiteral_782 ; is_production
il2cpp:00000001806FE029 call sub_180290560
il2cpp:00000001806FE02E lea rcx, StringLiteral_11321 ; device_identifier
il2cpp:00000001806FE035 call sub_180290560
il2cpp:00000001806FE03A lea rcx, StringLiteral_7302 ; app_version
il2cpp:00000001806FE041 call sub_180290560
il2cpp:00000001806FE046 lea rcx, StringLiteral_5200 ; random_discord
il2cpp:00000001806FE04D call sub_180290560
il2cpp:00000001806FE052 lea rcx, StringLiteral_12129 ; Discord Login
il2cpp:00000001806FE059 call sub_180290560
il2cpp:00000001806FE05E mov cs:byte_182F66094, 1
il2cpp:00000001806FE065
il2cpp:00000001806FE065 loc_1806FE065: ; CODE XREF: FJFKHBFABDJ$$MDMJCNKJPBD+1C↑j
il2cpp:00000001806FE065 mov rcx, cs:StringLiteral_14551 ; HHEFFLMAGMB
il2cpp:00000001806FE06C xor r8d, r8d ; method
il2cpp:00000001806FE06F mov rdx, rbp ; OIBNBEKDHJK
il2cpp:00000001806FE072 mov [rsp+28h+arg_0], rdi
il2cpp:00000001806FE077 call JLBNKGPMECB$$CKNKCBAAJAP
il2cpp:00000001806FE07C xor edx, edx ; method
il2cpp:00000001806FE07E mov rcx, rax ; value
il2cpp:00000001806FE081 mov rbx, rax
il2cpp:00000001806FE084 call System_String$$IsNullOrEmpty
il2cpp:00000001806FE089 test al, al
il2cpp:00000001806FE08B jnz loc_1806FE238
il2cpp:00000001806FE091 xor edx, edx ; method
il2cpp:00000001806FE093 mov rcx, rbx ; json
il2cpp:00000001806FE096 call Newtonsoft_Json_Linq_JObject$$Parse
il2cpp:00000001806FE09B mov rdi, rax
il2cpp:00000001806FE09E test rax, rax
il2cpp:00000001806FE0A1 jz loc_1806FE24F
il2cpp:00000001806FE0A7 mov rdx, cs:StringLiteral_11321 ; propertyName
il2cpp:00000001806FE0AE xor r8d, r8d ; method
il2cpp:00000001806FE0B1 mov rcx, rax ; this
il2cpp:00000001806FE0B4 call Newtonsoft_Json_Linq_JObject$$get_Item_6467784000
il2cpp:00000001806FE0B9 test rax, rax
il2cpp:00000001806FE0BC jz loc_1806FE24F
il2cpp:00000001806FE0C2 mov rdx, [rax]
il2cpp:00000001806FE0C5 mov rcx, rax
il2cpp:00000001806FE0C8 mov r8, [rdx+168h]
il2cpp:00000001806FE0CF mov rdx, [rdx+170h]
il2cpp:00000001806FE0D6 call r8
il2cpp:00000001806FE0D9 xor ecx, ecx ; method
il2cpp:00000001806FE0DB mov rbx, rax
il2cpp:00000001806FE0DE call UnityEngine_SystemInfo$$get_deviceUniqueIdentifier
il2cpp:00000001806FE0E3 xor r8d, r8d ; method
il2cpp:00000001806FE0E6 mov rdx, rax ; b
il2cpp:00000001806FE0E9 mov rcx, rbx ; a
il2cpp:00000001806FE0EC call System_String$$op_Inequality
il2cpp:00000001806FE0F1 test al, al
il2cpp:00000001806FE0F3 jnz loc_1806FE238
il2cpp:00000001806FE0F9 mov rdx, cs:StringLiteral_5200 ; propertyName
il2cpp:00000001806FE100 xor r8d, r8d ; method
il2cpp:00000001806FE103 mov rcx, rdi ; this
il2cpp:00000001806FE106 call Newtonsoft_Json_Linq_JObject$$get_Item_6467784000
il2cpp:00000001806FE10B test rax, rax
il2cpp:00000001806FE10E jz loc_1806FE24F
il2cpp:00000001806FE114 mov rdx, [rax]
il2cpp:00000001806FE117 mov rcx, rax
il2cpp:00000001806FE11A mov r8, [rdx+168h]
il2cpp:00000001806FE121 mov rdx, [rdx+170h]
il2cpp:00000001806FE128 call r8
il2cpp:00000001806FE12B mov rdx, [rsi+20h] ; b
il2cpp:00000001806FE12F xor r8d, r8d ; method
il2cpp:00000001806FE132 mov rcx, rax ; a
il2cpp:00000001806FE135 call System_String$$op_Inequality
il2cpp:00000001806FE13A test al, al
il2cpp:00000001806FE13C jnz loc_1806FE238
il2cpp:00000001806FE142 mov rdx, cs:StringLiteral_7302 ; propertyName
il2cpp:00000001806FE149 xor r8d, r8d ; method
il2cpp:00000001806FE14C mov rcx, rdi ; this
il2cpp:00000001806FE14F call Newtonsoft_Json_Linq_JObject$$get_Item_6467784000
il2cpp:00000001806FE154 test rax, rax
il2cpp:00000001806FE157 jz loc_1806FE24F
il2cpp:00000001806FE15D mov rdx, [rax]
il2cpp:00000001806FE160 mov rcx, rax
il2cpp:00000001806FE163 mov r8, [rdx+168h]
il2cpp:00000001806FE16A mov rdx, [rdx+170h]
il2cpp:00000001806FE171 call r8
il2cpp:00000001806FE174 mov rcx, cs:UnityEngine_Application_TypeInfo ; UnityEngine.Application_TypeInfo
il2cpp:00000001806FE17B mov rbx, rax
il2cpp:00000001806FE17E cmp dword ptr [rcx+0E4h], 0
il2cpp:00000001806FE185 jnz short loc_1806FE18C
il2cpp:00000001806FE187 call il2cpp_runtime_class_init
il2cpp:00000001806FE18C
il2cpp:00000001806FE18C loc_1806FE18C: ; CODE XREF: FJFKHBFABDJ$$MDMJCNKJPBD+1A5↑j
il2cpp:00000001806FE18C xor ecx, ecx ; method
il2cpp:00000001806FE18E call UnityEngine_Application$$get_version
il2cpp:00000001806FE193 xor r8d, r8d ; method
il2cpp:00000001806FE196 mov rdx, rax ; b
il2cpp:00000001806FE199 mov rcx, rbx ; a
il2cpp:00000001806FE19C call System_String$$op_Inequality
il2cpp:00000001806FE1A1 test al, al
il2cpp:00000001806FE1A3 jnz loc_1806FE238
il2cpp:00000001806FE1A9 mov rax, cs:StringLiteral_12129 ; Discord Login
il2cpp:00000001806FE1B0 lea rcx, [rsi+40h]
il2cpp:00000001806FE1B4 mov [rsi+40h], rax
il2cpp:00000001806FE1B8 mov rdx, cs:StringLiteral_12129 ; Discord Login
il2cpp:00000001806FE1BF call sub_18028F8B0
il2cpp:00000001806FE1C4 mov rcx, cs:UnityEngine_Application_TypeInfo ; UnityEngine.Application_TypeInfo
il2cpp:00000001806FE1CB cmp dword ptr [rcx+0E4h], 0
il2cpp:00000001806FE1D2 jnz short loc_1806FE1D9
il2cpp:00000001806FE1D4 call il2cpp_runtime_class_init
il2cpp:00000001806FE1D9
il2cpp:00000001806FE1D9 loc_1806FE1D9: ; CODE XREF: FJFKHBFABDJ$$MDMJCNKJPBD+1F2↑j
il2cpp:00000001806FE1D9 xor ecx, ecx ; method
il2cpp:00000001806FE1DB call UnityEngine_Application$$get_version
il2cpp:00000001806FE1E0 lea rcx, [rsi+30h]
il2cpp:00000001806FE1E4 mov [rsi+30h], rax
il2cpp:00000001806FE1E8 mov rdx, rax
il2cpp:00000001806FE1EB call sub_18028F8B0
il2cpp:00000001806FE1F0 lea rcx, [rsi+28h]
il2cpp:00000001806FE1F4 mov [rsi+28h], rbp
il2cpp:00000001806FE1F8 mov rdx, rbp
il2cpp:00000001806FE1FB call sub_18028F8B0
il2cpp:00000001806FE200 mov rdx, cs:StringLiteral_782 ; propertyName
il2cpp:00000001806FE207 xor r8d, r8d ; method
il2cpp:00000001806FE20A mov rcx, rdi ; this
il2cpp:00000001806FE20D call Newtonsoft_Json_Linq_JObject$$get_Item_6467784000
il2cpp:00000001806FE212 mov rdx, cs:Method$Newtonsoft_Json_Linq_Extensions_Value_bool___ ; method
il2cpp:00000001806FE219 mov rcx, rax ; value
il2cpp:00000001806FE21C call Newtonsoft_Json_Linq_Extensions$$Value_bool_
il2cpp:00000001806FE221 test al, al
il2cpp:00000001806FE223 mov edx, 1
il2cpp:00000001806FE228 mov ecx, 64h ; 'd'
il2cpp:00000001806FE22D movzx eax, dl
il2cpp:00000001806FE230 cmovz ecx, edx
il2cpp:00000001806FE233 mov [rsi+38h], ecx
il2cpp:00000001806FE236 jmp short loc_1806FE23A
il2cpp:00000001806FE238 ; ---------------------------------------------------------------------------
il2cpp:00000001806FE238
il2cpp:00000001806FE238 loc_1806FE238: ; CODE XREF: FJFKHBFABDJ$$MDMJCNKJPBD+AB↑j
il2cpp:00000001806FE238 ; FJFKHBFABDJ$$MDMJCNKJPBD+113↑j ...
il2cpp:00000001806FE238 xor al, al
il2cpp:00000001806FE23A
il2cpp:00000001806FE23A loc_1806FE23A: ; CODE XREF: FJFKHBFABDJ$$MDMJCNKJPBD+256↑j
il2cpp:00000001806FE23A mov rdi, [rsp+28h+arg_0]
il2cpp:00000001806FE23F mov rbx, [rsp+28h+arg_8]
il2cpp:00000001806FE244 mov rbp, [rsp+28h+arg_10]
il2cpp:00000001806FE249 add rsp, 20h
il2cpp:00000001806FE24D pop rsi
il2cpp:00000001806FE24E retn
il2cpp:00000001806FE24F ; ---------------------------------------------------------------------------
il2cpp:00000001806FE24F
il2cpp:00000001806FE24F loc_1806FE24F: ; CODE XREF: FJFKHBFABDJ$$MDMJCNKJPBD+C1↑j
il2cpp:00000001806FE24F ; FJFKHBFABDJ$$MDMJCNKJPBD+DC↑j ...
il2cpp:00000001806FE24F call sub_180290800
il2cpp:00000001806FE24F ; ---------------------------------------------------------------------------
il2cpp:00000001806FE254 db 0CCh
il2cpp:00000001806FE255 algn_1806FE255: ; DATA XREF: .pdata:0000000183289CD0↓o
il2cpp:00000001806FE255 align 20h
il2cpp:00000001806FE255 FJFKHBFABDJ$$MDMJCNKJPBD endp

笨笨程序员用了RSA加密,但是它放了私钥……StringLiteral_14551包含p,q,虽然不知道为什么私钥没写全,后面还有dq和d,但只知道p都够解密了。我说一点题外话,严重怀疑它们的程序员对加密只是一知半解,只知道公钥加密,私钥解密,客户端验证code需要解密,就把私钥放在客户端了()这里应该将私钥放在Discord,做一个签名,客户端用公钥验签才符合安全需求。

能不能给我个致谢

对应的F5反编译也放一放,

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
bool FJFKHBFABDJ__MDMJCNKJPBD(FJFKHBFABDJ_o *this, System_String_o *HBLNEJNOOBL, const MethodInfo *method)
{
__int64 v5; // rdx
__int64 v6; // rdx
__int64 v7; // rdx
__int64 v8; // rdx
__int64 v9; // rdx
__int64 v10; // rdx
__int64 v11; // rdx
System_String_o *v12; // rbx
Newtonsoft_Json_Linq_JObject_o *v13; // rax
Newtonsoft_Json_Linq_JObject_o *v14; // rdi
Newtonsoft_Json_Linq_JToken_o *Item_6467784000; // rax
System_String_o *v16; // rbx
System_String_o *deviceUniqueIdentifier; // rax
Newtonsoft_Json_Linq_JToken_o *v18; // rax
System_String_o *v19; // rax
Newtonsoft_Json_Linq_JToken_o *v20; // rax
System_String_o *v21; // rbx
System_String_o *version; // rax
struct System_String_o *v23; // rax
Newtonsoft_Json_Linq_JToken_o *v24; // rax
bool v25; // zf
int32_t v26; // ecx
bool result; // al

if ( !byte_182F66094 )
{
sub_180290560(&UnityEngine_Application_TypeInfo, HBLNEJNOOBL);
sub_180290560(&Method_Newtonsoft_Json_Linq_Extensions_Value_bool___, v5);
sub_180290560(&StringLiteral_14551, v6);
sub_180290560(&StringLiteral_782, v7);
sub_180290560(&StringLiteral_11321, v8);
sub_180290560(&StringLiteral_7302, v9);
sub_180290560(&StringLiteral_5200, v10);
sub_180290560(&StringLiteral_12129, v11);
byte_182F66094 = 1;
}
v12 = JLBNKGPMECB__CKNKCBAAJAP(StringLiteral_14551, HBLNEJNOOBL, 0);
if ( System_String__IsNullOrEmpty(v12, 0) )
return 0;
v13 = Newtonsoft_Json_Linq_JObject__Parse(v12, 0);
v14 = v13;
if ( !v13 )
goto LABEL_19;
Item_6467784000 = Newtonsoft_Json_Linq_JObject__get_Item_6467784000(v13, StringLiteral_11321, 0);
if ( !Item_6467784000 )
goto LABEL_19;
v16 = (System_String_o *)((__int64 (__fastcall *)(Newtonsoft_Json_Linq_JToken_o *, const MethodInfo *))Item_6467784000->klass->vtable._3_ToString.methodPtr)(
Item_6467784000,
Item_6467784000->klass->vtable._3_ToString.method);
deviceUniqueIdentifier = UnityEngine_SystemInfo__get_deviceUniqueIdentifier(0);
if ( System_String__op_Inequality(v16, deviceUniqueIdentifier, 0) )
return 0;
v18 = Newtonsoft_Json_Linq_JObject__get_Item_6467784000(v14, (System_String_o *)StringLiteral_5200, 0);
if ( !v18 )
goto LABEL_19;
v19 = (System_String_o *)((__int64 (__fastcall *)(Newtonsoft_Json_Linq_JToken_o *, const MethodInfo *))v18->klass->vtable._3_ToString.methodPtr)(
v18,
v18->klass->vtable._3_ToString.method);
if ( System_String__op_Inequality(v19, this->fields.discordRandomStr, 0) )
return 0;
v20 = Newtonsoft_Json_Linq_JObject__get_Item_6467784000(v14, StringLiteral_7302, 0);
if ( !v20 )
LABEL_19:
sub_180290800();
v21 = (System_String_o *)((__int64 (__fastcall *)(Newtonsoft_Json_Linq_JToken_o *, const MethodInfo *))v20->klass->vtable._3_ToString.methodPtr)(
v20,
v20->klass->vtable._3_ToString.method);
if ( !*(&UnityEngine_Application_TypeInfo->_2.cctor_finished + 1) )
il2cpp_runtime_class_init();
version = UnityEngine_Application__get_version(0);
if ( System_String__op_Inequality(v21, version, 0) )
return 0;
this->fields.LEFNABCJADK = StringLiteral_12129;
sub_18028F8B0(&this->fields.LEFNABCJADK, StringLiteral_12129);
if ( !*(&UnityEngine_Application_TypeInfo->_2.cctor_finished + 1) )
il2cpp_runtime_class_init();
v23 = UnityEngine_Application__get_version(0);
this->fields.loginVersion = v23;
sub_18028F8B0(&this->fields.loginVersion, v23);
this->fields.discordLoginSaveStr = HBLNEJNOOBL;
sub_18028F8B0(&this->fields.discordLoginSaveStr, HBLNEJNOOBL);
v24 = Newtonsoft_Json_Linq_JObject__get_Item_6467784000(v14, StringLiteral_782, 0);
v25 = !Newtonsoft_Json_Linq_Extensions__Value_bool_(
(System_Collections_Generic_IEnumerable_JToken__o *)v24,
Method_Newtonsoft_Json_Linq_Extensions_Value_bool___);
v26 = 100;
result = 1;
if ( v25 )
v26 = 1;
this->fields.CDPNMBDPOAM = v26;
return result;
}

我没细看,大致是调用JLBNKGPMECB__CKNKCBAAJAP函数进行解密,它是一个标准的base64解码+RSA OAEP,解密的结果经过Newtonsoft_Json_Linq_JObject__Parse解析为Json。

Newtonsoft_Json_Linq_JObject__get_Item_6467784000(System_String_o *)((__int64 (__fastcall *)(Newtonsoft_Json_Linq_JToken_o *, const MethodInfo *))xxx->klass->vtable._3_ToString.methodPtr)提取StringLiteral_11321 (device_identifier)StringLiteral_5200 (random_discord)StringLiteral_7302 (app_version),并和真实的deviceUniqueIdentifierthis->fields.discordRandomStrversion比对,只要有不一致就返回0。

以上三项通过后就验证完成了……才怪。程序先将this->fields.LEFNABCJADK设置为 Discord Loginthis->fields.loginVersion设置为当前版本,this->fields.discordLoginSaveStr设置为参数字符串,最后又获取了StringLiteral_782 (is_production),如果is_production = False,将v26设置为1,否则保持为100,更新字段this->fields.CDPNMBDPOAM

那么,Discord发回的验证码应该是使用公钥加密的,包含device_identifier,random_discord,app_version和is_production的Json字符串。由于我们也有公钥(有私钥就有公钥),我们现在的目标是构造上面这四个值。最后一项不在比对的范围内,因此最核心的目标是找到前三者。

现在我们假设自己是Discord的Bot,我该如何知道应该发回什么code呢?我需要用户提供的ID code。考虑到:同一用户可能使用不同设备,同一设备的random_discord可能不同,请求验证的版本可能不同,这样的话,ID code应当包含以上所有信息,才能覆盖全部可能,保证提供验证服务。

那么,我们来解密ID code吧!

ID code?似是故人来

复制出来的ID code大致如下,而且每次复制的都不一样。

1
1KyCcFHvVNeJCGEDnJ9/5HxqyhnlHerBWgrNwbrYnVXytJ860MwMBnYwMVSqeoKiv1DKB9Ts8a5zIWXdzuHi7oZXytc2gbk8/4fA6hoRlsYDjzRjBIgDyR1STaojYOrNh3DK8e8MysBjTM1jrI6u8eIDZOfj5qAtyRSBFCH90nsu/R+qoHcR4+4kmCztRoWqamR4rIJ1Z0A0rTphSIZnYZeSSX3fGol6qjmNeuZNsWwx8Oz7koBjwJhm0KCUE0w4eXkgN841yCt3r/ueCbwd+Pg98T4YESirlhe+qTzcmgUPWJoNRXEcgFZrYzwn/SFUubwurf/16D0e3Cmh3qwpPA==

看这个形式,估计还是一个base64,解码后试着用私钥解密一下发现解密不了,继续看吧。

让Gemini Cli分析了所有LoginView中的函数,梳理验证逻辑,它找到了生成并复制ID code的是函数Gamexxx_View_LoginView$$BCEKIAMPMOI,从这里开始继续往下。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
il2cpp:000000018068F590 ; void Gamexxx_View_LoginView__BCEKIAMPMOI(Gamexxx_View_LoginView_o *this, const MethodInfo *method)
il2cpp:000000018068F590 Gamexxx_View_LoginView$$BCEKIAMPMOI proc near
il2cpp:000000018068F590 ; DATA XREF: .data:0000000182D45298↓o
il2cpp:000000018068F590 ; .pdata:0000000183284C24↓o
il2cpp:000000018068F590
il2cpp:000000018068F590 var_8 = qword ptr -8
il2cpp:000000018068F590 NOFFKECEGCF = System_Nullable_float__o ptr 18h
il2cpp:000000018068F590
il2cpp:000000018068F590 sub rsp, 28h
il2cpp:000000018068F594 cmp cs:byte_182F65D60, 0
il2cpp:000000018068F59B jnz short loc_18068F5D4
il2cpp:000000018068F59D lea rcx, UnityEngine_GUIUtility_TypeInfo ; UnityEngine.GUIUtility_TypeInfo
il2cpp:000000018068F5A4 call sub_180290560
il2cpp:000000018068F5A9 lea rcx, Method$System_Nullable_float___ctor__ ; Method$System.Nullable<float>..ctor()
il2cpp:000000018068F5B0 call sub_180290560
il2cpp:000000018068F5B5 lea rcx, Singleton_ViewManager__TypeInfo ; Singleton<ViewManager>_TypeInfo
il2cpp:000000018068F5BC call sub_180290560
il2cpp:000000018068F5C1 lea rcx, StringLiteral_8571 ; The code has been copied
il2cpp:000000018068F5C8 call sub_180290560
il2cpp:000000018068F5CD mov cs:byte_182F65D60, 1
il2cpp:000000018068F5D4
il2cpp:000000018068F5D4 loc_18068F5D4: ; CODE XREF: Gamexxx_View_LoginView$$BCEKIAMPMOI+B↑j
il2cpp:000000018068F5D4 xor ecx, ecx ; method
il2cpp:000000018068F5D6 mov [rsp+28h+var_8], rbx
il2cpp:000000018068F5DB call MAPMGCMPMAM$$LMKINBNNDCN
il2cpp:000000018068F5E0 test rax, rax
il2cpp:000000018068F5E3 jz loc_18068F66E
il2cpp:000000018068F5E9 xor edx, edx ; method
il2cpp:000000018068F5EB mov rcx, rax ; this
il2cpp:000000018068F5EE call FJFKHBFABDJ$$FANOELKBMAM
il2cpp:000000018068F5F3 mov rcx, cs:UnityEngine_GUIUtility_TypeInfo ; UnityEngine.GUIUtility_TypeInfo
il2cpp:000000018068F5FA mov rbx, rax
il2cpp:000000018068F5FD cmp dword ptr [rcx+0E4h], 0
il2cpp:000000018068F604 jnz short loc_18068F60B
il2cpp:000000018068F606 call il2cpp_runtime_class_init
il2cpp:000000018068F60B
il2cpp:000000018068F60B loc_18068F60B: ; CODE XREF: Gamexxx_View_LoginView$$BCEKIAMPMOI+74↑j
il2cpp:000000018068F60B xor edx, edx ; method
il2cpp:000000018068F60D mov rcx, rbx ; value
il2cpp:000000018068F610 call UnityEngine_GUIUtility$$set_systemCopyBuffer
il2cpp:000000018068F615 mov rax, cs:Singleton_ViewManager__TypeInfo ; Singleton<ViewManager>_TypeInfo
il2cpp:000000018068F61C mov r8, cs:Method$System_Nullable_float___ctor__ ; method
il2cpp:000000018068F623 movss xmm1, cs:flt_1824F3C40 ; value
il2cpp:000000018068F62B mov rcx, [rax+0B8h]
il2cpp:000000018068F632 mov rbx, [rcx]
il2cpp:000000018068F635 lea rcx, [rsp+28h+NOFFKECEGCF] ; this
il2cpp:000000018068F63A mov qword ptr [rsp+28h+NOFFKECEGCF.fields.hasValue], 0
il2cpp:000000018068F643 call System_Nullable_float_$$_ctor
il2cpp:000000018068F648 test rbx, rbx
il2cpp:000000018068F64B jz short loc_18068F66E
il2cpp:000000018068F64D mov r8, qword ptr [rsp+28h+NOFFKECEGCF.fields.hasValue] ; NOFFKECEGCF
il2cpp:000000018068F652 xor r9d, r9d ; method
il2cpp:000000018068F655 mov rdx, cs:StringLiteral_8571 ; OIBNBEKDHJK
il2cpp:000000018068F65C mov rcx, rbx ; this
il2cpp:000000018068F65F call ViewManager$$ShowFadeOutTip
il2cpp:000000018068F664 mov rbx, [rsp+28h+var_8]
il2cpp:000000018068F669 add rsp, 28h
il2cpp:000000018068F66D retn
il2cpp:000000018068F66E ; ---------------------------------------------------------------------------
il2cpp:000000018068F66E
il2cpp:000000018068F66E loc_18068F66E: ; CODE XREF: Gamexxx_View_LoginView$$BCEKIAMPMOI+53↑j
il2cpp:000000018068F66E ; Gamexxx_View_LoginView$$BCEKIAMPMOI+BB↑j
il2cpp:000000018068F66E call sub_180290800
il2cpp:000000018068F66E ; ---------------------------------------------------------------------------
il2cpp:000000018068F673 db 0CCh
il2cpp:000000018068F674 algn_18068F674: ; DATA XREF: .pdata:0000000183284C24↓o
il2cpp:000000018068F674 align 20h
il2cpp:000000018068F674 Gamexxx_View_LoginView$$BCEKIAMPMOI endp

对应反编译:

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
void Gamexxx_View_LoginView__BCEKIAMPMOI(Gamexxx_View_LoginView_o *this, const MethodInfo *method)
{
__int64 v2; // rdx
__int64 v3; // rdx
__int64 v4; // rdx
FJFKHBFABDJ_o *v5; // rax
System_String_o *v6; // rbx
ViewManager_o *Instance; // rbx
System_Nullable_float__o NOFFKECEGCF; // [rsp+40h] [rbp+18h] BYREF

if ( !byte_182F65D60 )
{
sub_180290560(&UnityEngine_GUIUtility_TypeInfo, method);
sub_180290560(&Method_System_Nullable_float___ctor__, v2);
sub_180290560(&Singleton_ViewManager__TypeInfo, v3);
sub_180290560(&StringLiteral_8571, v4);
byte_182F65D60 = 1;
}
v5 = MAPMGCMPMAM__LMKINBNNDCN(0);
if ( !v5 )
goto LABEL_8;
v6 = FJFKHBFABDJ__FANOELKBMAM(v5, 0);
if ( !*(&UnityEngine_GUIUtility_TypeInfo->_2.cctor_finished + 1) )
il2cpp_runtime_class_init(UnityEngine_GUIUtility_TypeInfo);
UnityEngine_GUIUtility__set_systemCopyBuffer(v6, 0);
Instance = Singleton_ViewManager__TypeInfo->static_fields->Instance;
NOFFKECEGCF = 0;
System_Nullable_float____ctor((System_Nullable_float__o)&NOFFKECEGCF, 4.0, Method_System_Nullable_float___ctor__);
if ( !Instance )
LABEL_8:
sub_180290800();
ViewManager__ShowFadeOutTip(Instance, StringLiteral_8571, NOFFKECEGCF, 0);
}

还是熟悉的一堆sub_180290560,绑定若干外部值。使用MAPMGCMPMAM__LMKINBNNDCN先获取登录存档实例,再用FJFKHBFABDJ__FANOELKBMAM生成凭证。最后UnityEngine_GUIUtility__set_systemCopyBuffer将结果放到复制buffer,最后弹出提示,参数是StringLiteral_8571 (The code has been copied)

为什么前面Empty string之后有一个点,这里又没有……

看一下这俩函数吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FJFKHBFABDJ_o *MAPMGCMPMAM__LMKINBNNDCN(const MethodInfo *method)
{
FJFKHBFABDJ_o *result; // rax

if ( !byte_182F66BFE )
{
sub_180290560(&MAPMGCMPMAM_TypeInfo);
byte_182F66BFE = 1;
}
result = (FJFKHBFABDJ_o *)MAPMGCMPMAM_TypeInfo->static_fields->NLMHIDLAECP;
if ( result )
return (FJFKHBFABDJ_o *)result->fields.patreonLoginAccount;
return result;
}

它获取了MAPMGCMPMAM_TypeInfo->static_fields->NLMHIDLAECP;,如果获取到了,就取出它的fields.patreonLoginAccount存储的patreon登录信息,但是如果result为0,那返回result干什么……

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
il2cpp:0000000180805EE0 ; FJFKHBFABDJ_o *MAPMGCMPMAM__LMKINBNNDCN(const MethodInfo *method)
il2cpp:0000000180805EE0 MAPMGCMPMAM$$LMKINBNNDCN proc near ; CODE XREF: Gamexxx_View_LoginView$$BCEKIAMPMOI+4B↑p
il2cpp:0000000180805EE0 ; Gamexxx_View_LoginView$$BHOEBAPFFAD+4B↑p ...
il2cpp:0000000180805EE0 sub rsp, 28h
il2cpp:0000000180805EE4 cmp cs:byte_182F66BFE, 0
il2cpp:0000000180805EEB jnz short loc_180805F00
il2cpp:0000000180805EED lea rcx, MAPMGCMPMAM_TypeInfo ; MAPMGCMPMAM_TypeInfo
il2cpp:0000000180805EF4 call sub_180290560
il2cpp:0000000180805EF9 mov cs:byte_182F66BFE, 1
il2cpp:0000000180805F00
il2cpp:0000000180805F00 loc_180805F00: ; CODE XREF: MAPMGCMPMAM$$LMKINBNNDCN+B↑j
il2cpp:0000000180805F00 mov rax, cs:MAPMGCMPMAM_TypeInfo ; MAPMGCMPMAM_TypeInfo
il2cpp:0000000180805F07 mov rcx, [rax+0B8h]
il2cpp:0000000180805F0E mov rax, [rcx]
il2cpp:0000000180805F11 test rax, rax
il2cpp:0000000180805F14 jz short loc_180805F1F
il2cpp:0000000180805F16 mov rax, [rax+10h]
il2cpp:0000000180805F1A add rsp, 28h
il2cpp:0000000180805F1E retn
il2cpp:0000000180805F1F ; ---------------------------------------------------------------------------
il2cpp:0000000180805F1F
il2cpp:0000000180805F1F loc_180805F1F: ; CODE XREF: MAPMGCMPMAM$$LMKINBNNDCN+34↑j
il2cpp:0000000180805F1F add rsp, 28h
il2cpp:0000000180805F23 retn
il2cpp:0000000180805F23 ; ---------------------------------------------------------------------------
il2cpp:0000000180805F24 algn_180805F24: ; DATA XREF: .pdata:0000000183295AC0↓o
il2cpp:0000000180805F24 align 10h
il2cpp:0000000180805F24 MAPMGCMPMAM$$LMKINBNNDCN endp

额,汇编就长这样,那行吧,尊重编译器的决定。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
System_String_o *FJFKHBFABDJ__FANOELKBMAM(FJFKHBFABDJ_o *this, const MethodInfo *method)
{
__int64 v3; // rdx
__int64 v4; // rdx
__int64 v5; // rdx
__int64 v6; // rdx
__int64 v7; // rdx
__int64 v8; // rdx
Newtonsoft_Json_Linq_JObject_o *v9; // rbx
System_String_o *deviceUniqueIdentifier; // rsi
Newtonsoft_Json_Linq_JToken_o *v11; // rax
System_String_o *version; // rax
Newtonsoft_Json_Linq_JToken_o *v13; // rax
struct System_String_o *v14; // rax
Newtonsoft_Json_Linq_JToken_o *v15; // rax
System_String_o *v16; // rax

if ( !byte_182F66091 )
{
sub_180290560(&UnityEngine_Application_TypeInfo, method);
sub_180290560(&Newtonsoft_Json_Linq_JObject_TypeInfo, v3);
sub_180290560(&Newtonsoft_Json_Linq_JToken_TypeInfo, v4);
sub_180290560(&StringLiteral_11321, v5);
sub_180290560(&StringLiteral_7302, v6);
sub_180290560(&StringLiteral_5200, v7);
sub_180290560(&StringLiteral_14545, v8);
byte_182F66091 = 1;
}
v9 = (Newtonsoft_Json_Linq_JObject_o *)sub_1802907B0(Newtonsoft_Json_Linq_JObject_TypeInfo);
Newtonsoft_Json_Linq_JObject___ctor(v9, 0);
deviceUniqueIdentifier = UnityEngine_SystemInfo__get_deviceUniqueIdentifier(0);
if ( !*(&Newtonsoft_Json_Linq_JToken_TypeInfo->_2.cctor_finished + 1) )
il2cpp_runtime_class_init(Newtonsoft_Json_Linq_JToken_TypeInfo);
v11 = Newtonsoft_Json_Linq_JToken__op_Implicit_6467848256(deviceUniqueIdentifier, 0);
if ( !v9 )
sub_180290800();
Newtonsoft_Json_Linq_JObject__set_Item_6467784448(v9, StringLiteral_11321, v11, 0);
if ( !*(&UnityEngine_Application_TypeInfo->_2.cctor_finished + 1) )
il2cpp_runtime_class_init(UnityEngine_Application_TypeInfo);
version = UnityEngine_Application__get_version(0);
v13 = Newtonsoft_Json_Linq_JToken__op_Implicit_6467848256(version, 0);
Newtonsoft_Json_Linq_JObject__set_Item_6467784448(v9, StringLiteral_7302, v13, 0);
if ( System_String__IsNullOrEmpty(this->fields.discordRandomStr, 0) )
{
FJFKHBFABDJ__OCMPMHFEPIJ(this, 0);
v14 = JLBNKGPMECB__LNCFFFNFKPC(15, 0);
this->fields.discordRandomStr = v14;
sub_18028F8B0(&this->fields.discordRandomStr, v14);
MAPMGCMPMAM__NPIBGGPMOLD(1, 0);
}
v15 = Newtonsoft_Json_Linq_JToken__op_Implicit_6467848256(this->fields.discordRandomStr, 0);
Newtonsoft_Json_Linq_JObject__set_Item_6467784448(v9, (System_String_o *)StringLiteral_5200, v15, 0);
v16 = (System_String_o *)((__int64 (__fastcall *)(Newtonsoft_Json_Linq_JObject_o *, const MethodInfo *))v9->klass->vtable._3_ToString.methodPtr)(
v9,
v9->klass->vtable._3_ToString.method);
return JLBNKGPMECB__KLCJIOLLNCL(StringLiteral_14545, v16, 0);
}

第二个函数是真正拼凑信息的,deviceUniqueIdentifierversion直接用Unity的API获取,discordRandomStr如果为空,就新建一个15字符长的Str,保存到this->fields.discordRandomStr中。sub_18028F8B0看起来就很像在保存。

这样所有信息都齐全了,用(System_String_o *)((__int64 (__fastcall *)(Newtonsoft_Json_Linq_JObject_o *, const MethodInfo *))v9->klass->vtable._3_ToString.methodPtr)转换为字符串,并经过函数JLBNKGPMECB__KLCJIOLLNCL……等等,这个类名有点熟悉,前面解密的时候有一个JLBNKGPMECB__CKNKCBAAJAP,类是相同的,只是方法不同。

这个方法也很清晰,是用StringLiteral_14545作为公钥加密然后base64编码。这个密钥和刚才分析到的私钥不匹配,难怪解密不了。问题来了,这次是一个加密,程序员知道留下公钥,我没有私钥没法解密这些信息,那该怎么办?

字符串,Look at me!

既然公钥和私钥都硬编码在字符串里,那改一下字符串,能让程序直接用上吗?

StringLiteral_14551:你看看我呢

lookatme

IDA里没有这些字符串,前面提到了字符串存储在metadata中,但是其实到这个时候我想到要改了才去问()总之该去metadata里找了。

010

搜索 <RSA ,能搜到五处,一个个看,发现2E1…开头的是公钥(StringLiteral_14545)模数,kqb…开头的是私钥(StringLiteral_14551)模数,它们果然在metadata里。关于RSA就不再细说了,总之将公钥的模数(Modulus)和指数(Exponent)替换成私钥对应的部分,就可以用这个私钥解密。

修改过后的ID code就能够正确解密了,记得要去掉OAEP Padding,单纯解密得到的依然是乱码。

这样流程就十分清楚了,从ID code中获取信息,重组为一个新的Json,加密后填入验证框中。由于加解密使用的都是我们已知的密钥,我们自己AI就可以写一个注册机,替代Discord bot。

仅对当前版本(v0.2.27)有效,不保证前后版本兼容。使用文章中提到的编码方式(不是加密方式)进行了编码,看完文章或者有一些敏锐的感觉就能解了。

1
ZnJvbSBDcnlwdG8uUHVibGljS2V5IGltcG9ydCBSU0EgIyDlkb3ku6TooYzovpPlhaUgcGlwIGluc3RhbGwgcHljcnlwdG9kb21lIOWuieijheebuOW6lOeahOi9r+S7tuWMhQpmcm9tIENyeXB0by5DaXBoZXIgaW1wb3J0IFBLQ1MxX09BRVAKZnJvbSBDcnlwdG8uSGFzaCBpbXBvcnQgU0hBMQppbXBvcnQganNvbgppbXBvcnQgYmFzZTY0CmltcG9ydCBiaW5hc2NpaQoKZGVmIGRlY3J5cHRfSURjb2RlKHByaXZhdGVfa2V5X3BlbSwgYmFzZTY0X2VuY3J5cHRlZF9zdHIpOgogICAgIiIiCiAgICDop6Plr4blh73mlbDvvJrovpPlhaUgQmFzZTY0IOWtl+espuS4su+8jOi+k+WHuuino+aekOWQjueahCBKU09OIOWvueixoQogICAgIiIiCiAgICB0cnk6CiAgICAgICAgZW5jcnlwdGVkX2J5dGVzID0gYmFzZTY0LmI2NGRlY29kZShiYXNlNjRfZW5jcnlwdGVkX3N0cikKICAgICAgICBrZXkgPSBSU0EuaW1wb3J0X2tleShwcml2YXRlX2tleV9wZW0pCiAgICAgICAgCiAgICAgICAgIyDliJvlu7rop6Plr4blmagKICAgICAgICAjIOW/hemhu+aMh+WumiBoYXNoQWxnbz1TSEEx77yM5Zug5Li6QyPpu5jorqTliqDlr4bml7bnlKjnmoTmmK8gU0hBMQogICAgICAgIGNpcGhlciA9IFBLQ1MxX09BRVAubmV3KGtleSwgaGFzaEFsZ289U0hBMSkKICAgICAgICBkZWNyeXB0ZWRfYnl0ZXMgPSBjaXBoZXIuZGVjcnlwdChlbmNyeXB0ZWRfYnl0ZXMpCiAgICAgICAgCiAgICAgICAgIyDovazkuLrlrZfnrKbkuLLlubbop6PmnpAgSlNPTgogICAgICAgIGpzb25fc3RyID0gZGVjcnlwdGVkX2J5dGVzLmRlY29kZSgndXRmLTgnKQogICAgICAgIHBheWxvYWQgPSBqc29uLmxvYWRzKGpzb25fc3RyKQogICAgICAgIAogICAgICAgIHByaW50KGYiWytdIOino+WvhuaIkOWKnyEg5Y6f5aeLIFBheWxvYWQg5pWw5o2uOiIpCiAgICAgICAgcHJpbnQoanNvbi5kdW1wcyhwYXlsb2FkLCBpbmRlbnQ9NCwgZW5zdXJlX2FzY2lpPUZhbHNlKSkKICAgICAgICByZXR1cm4gcGF5bG9hZAoKICAgIGV4Y2VwdCBWYWx1ZUVycm9yIGFzIGU6CiAgICAgICAgcHJpbnQoZiJbIV0g6Kej5a+G5aSx6LSlOiDlr4bpkqXkuI3ljLnphY3miJbloavlhYXplJnor68gKFBhZGRpbmcgRXJyb3Ip44CC6K+m5oOFOiB7ZX0iKQogICAgZXhjZXB0IGJpbmFzY2lpLkVycm9yOgogICAgICAgIHByaW50KGYiWyFdIOino+WvhuWksei0pTog6L6T5YWl55qE5LiN5piv5pyJ5pWI55qEIEJhc2U2NCDlrZfnrKbkuLLjgIIiKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHByaW50KGYiWyFdIOWPkeeUn+acquefpemUmeivrzoge2V9IikKICAgIHJldHVybiBOb25lCgpkZWYgZ2VuZXJhdGVfbGljZW5zZShwcml2YXRlX2tleV9wZW0sIGRldmljZV9pZCwgcmFuZG9tX2Rpc2NvcmQsIGFwcF92ZXJzaW9uKToKICAgICIiIgogICAg5Yqg5a+G5Ye95pWw77ya6L6T5YWl5L+h5oGv77yM6L6T5Ye65Yqg5a+G5ZCO55qEIEJhc2U2NCDlrZfnrKbkuLIKICAgICIiIgogICAgdHJ5OgogICAgICAgIGtleSA9IFJTQS5pbXBvcnRfa2V5KHByaXZhdGVfa2V5X3BlbSkKICAgICAgICAKICAgICAgICBwYXlsb2FkID0gewogICAgICAgICAgICAiZGV2aWNlX2lkZW50aWZpZXIiOiBkZXZpY2VfaWQsCiAgICAgICAgICAgICJhcHBfdmVyc2lvbiI6IGFwcF92ZXJzaW9uLAogICAgICAgICAgICAicmFuZG9tX2Rpc2NvcmQiOiByYW5kb21fZGlzY29yZCwKICAgICAgICAgICAgImlzX3Byb2R1Y3Rpb24iOiBUcnVlCiAgICAgICAgfQogICAgICAgIAogICAgICAgIGpzb25fZGF0YSA9IGpzb24uZHVtcHMocGF5bG9hZCkKICAgICAgICBkYXRhX2J5dGVzID0ganNvbl9kYXRhLmVuY29kZSgndXRmLTgnKQogICAgICAgIAogICAgICAgIGNpcGhlciA9IFBLQ1MxX09BRVAubmV3KGtleSwgaGFzaEFsZ289U0hBMSkKICAgICAgICBlbmNyeXB0ZWRfYnl0ZXMgPSBjaXBoZXIuZW5jcnlwdChkYXRhX2J5dGVzKQogICAgICAgIGxpY2Vuc2Vfa2V5ID0gYmFzZTY0LmI2NGVuY29kZShlbmNyeXB0ZWRfYnl0ZXMpLmRlY29kZSgndXRmLTgnKQogICAgICAgIAogICAgICAgIHJldHVybiBsaWNlbnNlX2tleQoKICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICBwcmludChmIlshXSDliqDlr4bnlJ/miJDlpLHotKU6IHtlfSIpCiAgICByZXR1cm4gTm9uZQoKIyA9PT09PT09PT09PT09PT09PSDphY3nva7ljLrln58gPT09PT09PT09PT09PT09PT0KCk1ZX1BSSVZBVEVfS0VZID0gIiIiLS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBa3FiN1lLRlNvVmZ3cGpmWm96T2YrL0NCV1pyNTJpNnZHeHoycWlHaCtqeUI1STBmCndsU0s5aExJRmR4ZkpDZ25id25YNkc3RWFqMVhaN3pYRFdnN2lJSTdGRWl3SE1ObW9zS2dwTXNmbm9GRjhvdzQKVDQrcHptdllKUzBEU09ZTlp5VHpsMHdub0QwU2YyRmFmdnRsbHROdjh0TlpHU28vM205Z1RGN2gxWjZnZkpQagpud1lKV0ZrWU5icDhFTUtoZ3JqdTVYRWxKcVpJTEhmYmI0a09ReDlqSWY3NDVZTHN4ajhubEk3RmU0bitmTjJtCjVPdHdlVGo0NXpnWngzc0ZrbVBaMUJHUDRreFFTY0JYVXR5NnNjaW9Vc2tNWkRaTHZjMjEweDMyWkVMb0xhajMKcjFlem52dXJmSnQ4MVJBRXR0Q1E3dTFFMWo4cCtKbUNvODFNalFJREFRQUJBb0lCQVFDRkRUQjJMQWJ1MFAxSgpvTlE1Q0VaMjZtNUFvS1ZjZVF4dldlakUxRHpEN28rZWp4Y29WVkc2RGJJd2V6NXFiRy9TZ3lpNi9Yd0FNMlVRCmZqalNOaVlGSkxwN0hiT0NkUm15UTBoQ0d2TzE5KzFCTUV5S2lubnlDTkFTY25OUGVVRGh1cG15UStxVHE3VVQKa256clJpU1dJWUNvMjIwenNxdzluK2pMZ1MzOWcwd2tOWkRMa0t4dE11ZVROZk5wUlBxZC8zT05nMkNFK3IxVwpwaVNoRWJuZ2dQNVAyVWkyMkVXa1JVSzZpeDJ5VmZWdzdLVXdBQjBTVFRMTmJsNW1lYWRPTWUxdXRjb0syUmFQClNUZnlGMDA1S3IxcDkyby9RZWszNmllK1RVOEYvSm1xd1VFUHBITTdjcUVDOEc0b0dPdzNRYTdocXlnUDhXY3MKakJNaEEybGhBb0dCQU02R3NidDgyQnhsa0dqTDExNEJWUmlEVzMrYkJSYTN5VDlkT0hyZyswMFVLTUlZRmRwSQo0S3p3T3VZM3ViNStudGZHRVAwcVFvS01SVVJmTkJhSWNJZEx3d0dvck0zV1l4SkdTM3pzelc1bnlxNTY2WmowCjgxSkFKVFBSUWlxc3hXUDBUTFA2MWRJd0p3TmZTN3pMdFpiRnlvbFF0YjE4Mk1IWVA3M25GQ0FKQW9HQkFMWEkKZndrcStsRFQzNWRranh4QlhIb3Z6a1FxeHUySWhlUFZGVytzS1RuY2FSaDE4UHpTWTByY0trL0FuZVlYazBnUApnVXVDWmhZZGs3V29HWFJwa09sYW9XNTYzaDBRN0JDY1N5cVlmU2F6T2h1eTFWN3dCbkVSWXZNcGdOZEhyOS8rCmQwcHlySnJBQzdOTU5xN2s1TjcraFkyc0ZKWW1xN2RLcFk0YXY2RmxBb0dBY09rbkJtL3MzeW1QMkJ1SnRYVloKQnJ2YUxGdWVYcHRBUllvN3RNS0g0YzRoc212a2xxQzlaUTg1eEYrM0J6VEd0d1VzaUp5d0hCdUFTeThabFRBbgpCWEVnUGludnYrRno3S3ZOM1pEQmg1anJNSlUyWFUrZUw5dXQrelJ1emxJQzRQRFJkcHl1aGVtWmlyaHcrZGtOCmdFN0d1bWdMMUhzTExuK0I2ZHNLSDdFQ2dZRUFuWkFVZEhqWkVOZ2t0SStINytERGJIRWxZemFFeUh5T09PV0kKT0F4TWk0ZDlYS05WNTMzZ2JKR2E1eGU0aFNpb0UwekZNQkx0czN1ZGZWWWdBaVJDWTlHWG5UQklkMzhrYmwxYQpWYWhveWZPTmlWYWYxVE5ORWhJVTUxbHhBS2gxVER5L1h6czRXc1hkSG1NUC90WU5meSt6eVJGL29vb1FTc1liCjY2bEpzblVDZ1lBdXRjL3Q0UVBYeVVlL2FSVkc4ODBtMkdBWVBIYjNPcGFJUnRRdUNpQ0xlM2p6S0R0QnR4RFoKTXlQbWtUQ1g1Mk40SVExeDhuRlF3eWppdnJ1elNQOFVaYzBsSkJKMTJqNVpIckhrVHJIdXNnUk9qOHoySWRBOQpaZnkvUzF4ckRJb0IyZ01kOFh1OGh1M0FMS1ZDZjJWWGhBQnMvSmRYdHR2UUlYTXA4SzVzN2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQoiIiIKCiMgPT09PT09PT09PT09PT09PT0g5Li756iL5bqP5YWl5Y+jID09PT09PT09PT09PT09PT09CgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgcHJpbnQoIj09PSBVbml0eSBHYW1lIExpY2Vuc2UgVG9vbCAoRGVjcnlwdCAmIFJlLWVuY3J5cHQpID09PVxuIikKICAgIAogICAgIyAxLiDovpPlhaXliqDlr4ZJRCBjb2RlCiAgICB0YXJnZXRfZW5jcnlwdGVkX3N0ciA9ICcuLi4nCgogICAgcHJpbnQoIi0iICogNjApCiAgICBwcmludCgiU3RlcCAxOiDmraPlnKjlsJ3or5Xop6Plr4YuLi4iKQogICAgCiAgICAjIDIuIOiwg+eUqOino+WvhgogICAgZGVjcnlwdGVkX2RhdGEgPSBkZWNyeXB0X0lEY29kZShNWV9QUklWQVRFX0tFWSwgdGFyZ2V0X2VuY3J5cHRlZF9zdHIpCiAgICAKICAgIGlmIGRlY3J5cHRlZF9kYXRhOgogICAgICAgIHByaW50KCItIiAqIDYwKQogICAgICAgIHByaW50KCJTdGVwIDI6IOS9v+eUqOino+WvhuaVsOaNrumHjeaWsOWKoOWvhi4uLiIpCiAgICAgICAgCiAgICAgICAgIyDku47op6Plr4bmlbDmja7kuK3mj5Dlj5blrZfmrrUKICAgICAgICBkZXZpY2VfaWQgPSBkZWNyeXB0ZWRfZGF0YS5nZXQoImRldmljZV9pZGVudGlmaWVyIiwgIiIpCiAgICAgICAgcmFuZG9tX2Rpc2NvcmQgPSBkZWNyeXB0ZWRfZGF0YS5nZXQoInJhbmRvbV9kaXNjb3JkIiwgIiIpCiAgICAgICAgYXBwX3ZlcnNpb24gPSBkZWNyeXB0ZWRfZGF0YS5nZXQoImFwcF92ZXJzaW9uIiwgIiIpCiAgICAgICAgCiAgICAgICAgIyAzLiDph43mlrDliqDlr4YKICAgICAgICBuZXdfbGljZW5zZSA9IGdlbmVyYXRlX2xpY2Vuc2UoCiAgICAgICAgICAgIE1ZX1BSSVZBVEVfS0VZLCAKICAgICAgICAgICAgZGV2aWNlX2lkLCAKICAgICAgICAgICAgcmFuZG9tX2Rpc2NvcmQsIAogICAgICAgICAgICBhcHBfdmVyc2lvbgogICAgICAgICkKICAgICAgICAKICAgICAgICBpZiBuZXdfbGljZW5zZToKICAgICAgICAgICAgcHJpbnQoZiJcblsrXSDliqDlr4bmiJDlip/vvIFjb2Rl5aaC5LiL77yaIikKICAgICAgICAgICAgcHJpbnQoIi0iICogNjApCiAgICAgICAgICAgIHByaW50KG5ld19saWNlbnNlKQogICAgICAgICAgICBwcmludCgiLSIgKiA2MCk=

后记

我终于不用动控制流了……大概吧,控制流留给懂的人看就好。我老老实实(其实不然)按照步骤验证就行,反正程序也不知道,程序会认为我真的从bot那里获得了码。

看到RSA属实是虎躯一震,我一个搞密码学的最喜欢它,然后看到了私钥()怎么会有这种事情,做梦都要笑醒。不过即使客户端只存了公钥也没关系,毕竟我可以改,自己生成一组密钥就行了,宗旨是提取出信息。

这种邪恶的方法肯定不是我先想到的(×)从这里得到的灵感,第一次看到也是惊为天人,再加上这里密钥全是可打印字符串,更好动手脚了。希望这个方法能存活久一点喵~