Last active
February 3, 2026 20:10
-
-
Save EastArctica/84baf99dfab7b5db9fc733793b0f4252 to your computer and use it in GitHub Desktop.
Discord launch signature generation reverse engineering. Used for client mod detection and reporting. Includes ai written launch_signature.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| void detect_client_and_set_flag | |
| (int bitfield,uint clientmod_id,undefined4 js_context,HeapStr *strs,int totalStrs) | |
| { | |
| bool bVar1; | |
| uint size; | |
| uint key_0; | |
| int iVar2; | |
| int remaining; | |
| byte *pbVar3; | |
| uint uVar4; | |
| uint status_value; | |
| uint local_14; | |
| astruct decoded_str; | |
| uint key; | |
| /* 24 for BD */ | |
| remaining = totalStrs << 3; | |
| do { | |
| if (remaining == 0) { | |
| /* 2 for BD */ | |
| pbVar3 = (byte *)(bitfield + (clientmod_id >> 3)); | |
| uVar4 = (clientmod_id ^ 0xffffffff) & 7; | |
| *pbVar3 = *pbVar3 & ((byte)(-2 << uVar4) | (byte)(0xfffffffe >> 0x20 - uVar4)); | |
| return; | |
| } | |
| decode_hex_xor73(&decoded_str,strs->ptr,strs->len); | |
| size = decoded_str.size; | |
| key_0 = new_string(decoded_str.size,decoded_str.dest_0); | |
| key = key_0; | |
| js_get_property_checked(&status_value,js_context,&key); | |
| uVar4 = local_14; | |
| if ((status_value & 1) == 0) { | |
| iVar2 = isUndefined(local_14); | |
| bVar1 = iVar2 == 1; | |
| } | |
| else { | |
| bVar1 = true; | |
| } | |
| if (0x83 < uVar4) { | |
| drop_js_handle(uVar4); | |
| } | |
| if (0x83 < key_0) { | |
| drop_js_handle(key_0); | |
| } | |
| if (decoded_str.dest != 0) { | |
| dlmalloc::validate_size(size,decoded_str.dest,1); | |
| } | |
| strs = strs + 1; | |
| remaining = remaining + -8; | |
| } while (bVar1); | |
| pbVar3 = (byte *)(bitfield + (clientmod_id >> 3)); | |
| *pbVar3 = *pbVar3 | (byte)(1 << ((clientmod_id ^ 0xffffffff) & 7)); | |
| return; | |
| } | |
| uint export::generateLaunchSignature(undefined4 globalVar) | |
| { | |
| uint uVar1; | |
| ulonglong uVar2; | |
| ulonglong uVar3; | |
| uint uVar4; | |
| uint finalUuid; | |
| uint someMem; | |
| int iVar5; | |
| undefined8 local_b8; | |
| undefined1 local_b0; | |
| undefined1 local_af; | |
| undefined1 local_ae; | |
| undefined1 local_ad; | |
| undefined1 local_ac; | |
| undefined1 uStack_ab; | |
| undefined1 uStack_aa; | |
| undefined1 uStack_a9; | |
| undefined1 local_a8; | |
| undefined1 uStack_a7; | |
| undefined1 uStack_a6; | |
| undefined1 uStack_a5; | |
| undefined4 local_a4; | |
| undefined8 random_values; | |
| undefined8 local_98; | |
| uint local_88; | |
| uint local_84; | |
| uint local_80; | |
| uint local_7c; | |
| uint jsContext; | |
| ulonglong bitfield; | |
| ulonglong local_68; | |
| byte jqueryStr2; | |
| byte local_5f; | |
| byte local_5e; | |
| byte local_5d; | |
| byte local_5c; | |
| byte local_5b; | |
| byte local_5a; | |
| byte local_59; | |
| byte local_58; | |
| byte local_57; | |
| byte local_56; | |
| byte local_55; | |
| byte local_54; | |
| byte local_53; | |
| byte local_52; | |
| byte local_51; | |
| astruct jqueryStr; | |
| astruct dollarStr; | |
| astruct *targetStrLen; | |
| undefined *targeStrRaw; | |
| undefined8 local_2c; | |
| uint local_24; | |
| uint local_20; | |
| astruct fnStr [2]; | |
| someMem = unnamed_function_210(); | |
| *(undefined4 *)((ulonglong)someMem * 4 + 0x100000000) = globalVar; | |
| local_98 = 0; | |
| random_values = 0; | |
| jsContext = someMem; | |
| import::./libdiscore_wasm_bg.js::__wbg_getRandomValues_38097e921c2494c3(&random_values,0x10); | |
| uVar1 = DAT_ram_0012c014._4_4_; | |
| /* previous state */ | |
| iVar5 = (int)DAT_ram_0012c014; | |
| DAT_ram_0012c014 = 0; | |
| if (iVar5 == 1) { | |
| if (0x83 < uVar1) { | |
| drop_js_handle(uVar1); | |
| } | |
| local_a4 = &PTR_s_/root/.cargo/registry/src/index._ram_001016ef_ram_00103530; | |
| local_a8 = 0x28; | |
| uStack_a7 = 0; | |
| uStack_a6 = 0; | |
| uStack_a5 = 0; | |
| local_ac = 8; | |
| uStack_ab = 0x35; | |
| uStack_aa = 0x10; | |
| uStack_a9 = 0; | |
| local_b8 = 0x2800103508; | |
| unnamed_function_380 | |
| (&local_b8,&DAT_ram_0012964c, | |
| &PTR_s_/root/.cargo/registry/src/index._ram_001016ef_ram_00103530,1,0); | |
| do { | |
| halt_trap(); | |
| } while( true ); | |
| } | |
| local_68 = random_values << 0x38 | (random_values & 0xff00) << 0x28 | | |
| (random_values & 0xff0000) << 0x18 | (random_values & 0xff000000) << 8 | | |
| random_values >> 8 & 0xff000000 | random_values >> 0x18 & 0xff0000 | | |
| random_values >> 0x28 & 0xff00 | | |
| (random_values & 0x3f00000000000000 | 0x8000000000000000) >> 0x38; | |
| bitfield = local_98 << 0x38 | (local_98 & 0xf00 | 0x4000) << 0x28 | | |
| (local_98 & 0xff0000) << 0x18 | (local_98 & 0xff000000) << 8 | | |
| local_98 >> 8 & 0xff000000 | local_98 >> 0x18 & 0xff0000 | | |
| local_98 >> 0x28 & 0xff00 | local_98 >> 0x38; | |
| decode_hex_xor73(&jqueryStr,jquery_hex_obf,0xc); | |
| decode_hex_xor73(&dollarStr,dollar_hex_ofb,2); | |
| decode_hex_xor73(fnStr,fn_hex_obf,4); | |
| decode_hex_xor73((astruct *)&jqueryStr2,jquery_hex_obf_2,0xc); | |
| local_20 = _local_58; | |
| uVar1 = _local_5c; | |
| local_24 = _local_5c; | |
| uVar4 = fnStr[0].size; | |
| local_2c = CONCAT44(fnStr[0].dest_0,fnStr[0].size); | |
| targeStrRaw = (undefined *)jqueryStr.dest_0; | |
| targetStrLen = (astruct *)jqueryStr.size; | |
| unnamed_function_308(&local_80,someMem,&targetStrLen); | |
| if ((local_80 & 1) == 0) { | |
| code_r0x80064b52: | |
| finalUuid = dollarStr.size; | |
| local_20 = _local_58; | |
| local_24 = uVar1; | |
| local_2c = CONCAT44(fnStr[0].dest_0,uVar4); | |
| targeStrRaw = (undefined *)dollarStr.dest_0; | |
| targetStrLen = (astruct *)dollarStr.size; | |
| unnamed_function_308(&local_88,someMem,&targetStrLen); | |
| if ((local_88 & 1) != 0) { | |
| iVar5 = unnamed_function_1028(local_84); | |
| if ((iVar5 != 1) && (iVar5 = isUndefined(local_84), iVar5 != 1)) { | |
| if (0x83 < local_84) { | |
| drop_js_handle(local_84); | |
| } | |
| goto code_r0x80064c44; | |
| } | |
| if (0x83 < local_84) { | |
| drop_js_handle(local_84); | |
| } | |
| } | |
| if (_jqueryStr2 != 0) { | |
| dlmalloc::validate_size(uVar1,_jqueryStr2,1); | |
| } | |
| if (fnStr[0].dest != 0) { | |
| dlmalloc::validate_size(uVar4,fnStr[0].dest,1); | |
| } | |
| if (dollarStr.dest != 0) { | |
| dlmalloc::validate_size(finalUuid,dollarStr.dest,1); | |
| } | |
| if (jqueryStr.dest != 0) { | |
| dlmalloc::validate_size(jqueryStr.size,jqueryStr.dest,1); | |
| } | |
| bitfield = bitfield & 0xffffffffffff7fff; | |
| } | |
| else { | |
| iVar5 = unnamed_function_1028(local_7c); | |
| if ((iVar5 == 1) || (iVar5 = isUndefined(local_7c), iVar5 == 1)) { | |
| if (0x83 < local_7c) { | |
| drop_js_handle(local_7c); | |
| } | |
| goto code_r0x80064b52; | |
| } | |
| if (0x83 < local_7c) { | |
| drop_js_handle(local_7c); | |
| } | |
| code_r0x80064c44: | |
| if (_jqueryStr2 != 0) { | |
| dlmalloc::validate_size(uVar1,_jqueryStr2,1); | |
| } | |
| if (fnStr[0].dest != 0) { | |
| dlmalloc::validate_size(uVar4,fnStr[0].dest,1); | |
| } | |
| if (dollarStr.dest != 0) { | |
| dlmalloc::validate_size(dollarStr.size,dollarStr.dest,1); | |
| } | |
| if (jqueryStr.dest != 0) { | |
| dlmalloc::validate_size(jqueryStr.size,jqueryStr.dest,1); | |
| } | |
| bitfield = bitfield | 0x8000; | |
| } | |
| detect_client_and_set_flag(&bitfield,0b00010011,&jsContext,BetterDiscordStrs,3); | |
| detect_client_and_set_flag(&bitfield,0b00011011,&jsContext,RamboxStrs,1); | |
| detect_client_and_set_flag(&bitfield,0b00100100,&jsContext,RevengeStrs,4); | |
| detect_client_and_set_flag(&bitfield,0b00101011,&jsContext,VencordStrs,4); | |
| detect_client_and_set_flag(&bitfield,0b00110100,&jsContext,RepluggedStrs,2); | |
| detect_client_and_set_flag(&bitfield,0b01000010,&jsContext,LegcordStrs,2); | |
| detect_client_and_set_flag(&bitfield,0b01001000,&jsContext,DorionStrs,3); | |
| detect_client_and_set_flag(&bitfield,0b01001111,&jsContext,GCDPStrs,1); | |
| detect_client_and_set_flag(&bitfield,0b01011001,&jsContext,OpenasarStrs,1); | |
| detect_client_and_set_flag(&bitfield,0b01100111,&jsContext,ShelterStrs,1); | |
| detect_client_and_set_flag(&bitfield,0b01110100,&jsContext,MoonlightStrs,2); | |
| _local_58 = local_68; | |
| uVar3 = _local_58; | |
| _jqueryStr2 = bitfield; | |
| uVar2 = _jqueryStr2; | |
| fnStr[0].dest_0 = 0; | |
| fnStr[0].dest = 0; | |
| fnStr[0].size = 1; | |
| targeStrRaw = &DAT_ram_00102a54; | |
| local_2c = 0x60000020; | |
| targetStrLen = fnStr; | |
| local_ac = 0x2d; | |
| uStack_a7 = 0x2d; | |
| jqueryStr2 = (byte)bitfield; | |
| local_b8._0_5_ = CONCAT14((&DAT_ram_001034e8)[jqueryStr2 >> 4],(undefined4)local_b8); | |
| local_5f = (byte)(bitfield >> 8); | |
| local_b8 = CONCAT17((&DAT_ram_001034e8)[local_5f & 0xf], | |
| CONCAT16((&DAT_ram_001034e8)[local_5f >> 4], | |
| CONCAT15((&DAT_ram_001034e8)[jqueryStr2 & 0xf],(undefined5)local_b8)) | |
| ); | |
| local_5e = (byte)(bitfield >> 0x10); | |
| local_af = (&DAT_ram_001034e8)[local_5e & 0xf]; | |
| local_b0 = (&DAT_ram_001034e8)[local_5e >> 4]; | |
| local_5d = (byte)(bitfield >> 0x18); | |
| local_ad = (&DAT_ram_001034e8)[local_5d & 0xf]; | |
| local_ae = (&DAT_ram_001034e8)[local_5d >> 4]; | |
| local_5c = (byte)(bitfield >> 0x20); | |
| uStack_aa = (&DAT_ram_001034e8)[local_5c & 0xf]; | |
| uStack_ab = (&DAT_ram_001034e8)[local_5c >> 4]; | |
| local_5b = (byte)(bitfield >> 0x28); | |
| local_a8 = (&DAT_ram_001034e8)[local_5b & 0xf]; | |
| uStack_a9 = (&DAT_ram_001034e8)[local_5b >> 4]; | |
| local_5a = (byte)(bitfield >> 0x30); | |
| uStack_a6 = (&DAT_ram_001034e8)[local_5a >> 4]; | |
| uStack_a5 = (&DAT_ram_001034e8)[local_5a & 0xf]; | |
| local_59 = (byte)(bitfield >> 0x38); | |
| local_a4 = (undefined **) | |
| CONCAT22(0x2d,CONCAT11((&DAT_ram_001034e8)[local_59 & 0xf], | |
| (&DAT_ram_001034e8)[local_59 >> 4])); | |
| local_58 = (byte)local_68; | |
| local_a4 = (undefined **)CONCAT13((&DAT_ram_001034e8)[local_58 >> 4],(undefined3)local_a4); | |
| local_57 = (byte)(local_68 >> 8); | |
| random_values._0_3_ = | |
| CONCAT12((&DAT_ram_001034e8)[local_57 & 0xf], | |
| CONCAT11((&DAT_ram_001034e8)[local_57 >> 4],(&DAT_ram_001034e8)[local_58 & 0xf])); | |
| local_56 = (byte)(local_68 >> 0x10); | |
| random_values._0_5_ = | |
| CONCAT14((&DAT_ram_001034e8)[local_56 >> 4],CONCAT13(0x2d,(undefined3)random_values)); | |
| local_55 = (byte)(local_68 >> 0x18); | |
| random_values = | |
| CONCAT17((&DAT_ram_001034e8)[local_55 & 0xf], | |
| CONCAT16((&DAT_ram_001034e8)[local_55 >> 4], | |
| CONCAT15((&DAT_ram_001034e8)[local_56 & 0xf],(undefined5)random_values))); | |
| local_54 = (byte)(local_68 >> 0x20); | |
| local_53 = (byte)(local_68 >> 0x28); | |
| local_52 = (byte)(local_68 >> 0x30); | |
| local_51 = (byte)(local_68 >> 0x38); | |
| local_98 = CONCAT17((&DAT_ram_001034e8)[local_51 & 0xf], | |
| CONCAT16((&DAT_ram_001034e8)[local_51 >> 4], | |
| CONCAT15((&DAT_ram_001034e8)[local_52 & 0xf], | |
| CONCAT14((&DAT_ram_001034e8)[local_52 >> 4], | |
| CONCAT13((&DAT_ram_001034e8)[local_53 & 0xf], | |
| CONCAT12((&DAT_ram_001034e8) | |
| [local_53 >> 4], | |
| CONCAT11((&DAT_ram_001034e8) | |
| [local_54 & 0xf], | |
| (&DAT_ram_001034e8) | |
| [local_54 >> 4]))))))); | |
| _jqueryStr2 = uVar2; | |
| _local_58 = uVar3; | |
| iVar5 = unnamed_function_864(&targetStrLen,(int)&local_b8 + 4,0x24); | |
| uVar1 = fnStr[0].dest_0; | |
| if (iVar5 != 0) { | |
| unnamed_function_524 | |
| (s_a_Display_implementation_returne_ram_00102a7c,0x37,&dollarStr,&DAT_ram_00102a6c, | |
| &PTR_s_/nix/store/lmp2ykgvp9m441iwxcjs8_ram_00101741_ram_00102ab4); | |
| do { | |
| halt_trap(); | |
| } while( true ); | |
| } | |
| finalUuid = fnStr[0].size; | |
| uVar4 = fnStr[0].dest; | |
| if (0x83 < someMem) { | |
| drop_js_handle(someMem); | |
| } | |
| if (uVar1 < uVar4) { | |
| if (uVar1 == 0) { | |
| dlmalloc::validate_size(finalUuid,uVar4,1); | |
| finalUuid = 1; | |
| } | |
| else { | |
| finalUuid = dlmalloc::realloc(finalUuid,uVar4,1,uVar1); | |
| if (finalUuid == 0) { | |
| panic_capacity_overflow(1,uVar1); | |
| do { | |
| halt_trap(); | |
| } while( true ); | |
| } | |
| } | |
| } | |
| return finalUuid; | |
| } | |
| void decode_hex_xor73(astruct *ret_data,int hex_str,uint hex_str_len) | |
| { | |
| int iVar1; | |
| undefined2 uVar2; | |
| undefined1 uVar3; | |
| uint size; | |
| uint uVar4; | |
| byte *pbVar5; | |
| uint uVar6; | |
| uint uVar7; | |
| uint uVar8; | |
| int local_4c; | |
| uint local_48; | |
| uint local_44; | |
| int local_40; | |
| uint local_3c; | |
| undefined8 local_38; | |
| int local_30; | |
| uint local_2c; | |
| int local_24; | |
| uint local_18; | |
| int local_14; | |
| uint local_10; | |
| uint local_c; | |
| int str_0; | |
| uint dst_0; | |
| uint dst; | |
| int src; | |
| dst = 0; | |
| local_4c = 1; | |
| if ((hex_str_len & 1) == 0) { | |
| size = hex_str_len >> 1; | |
| if ((hex_str_len != 0) && (local_4c = unnamed_function_955(size,1), local_4c == 0)) { | |
| panic_capacity_overflow(1,size); | |
| do { | |
| halt_trap(); | |
| } while( true ); | |
| } | |
| local_10 = 0; | |
| local_18 = size; | |
| uVar4 = 0; | |
| for (; local_14 = local_4c, hex_str_len != size; size = size + 1) { | |
| if (uVar4 + 1 < hex_str_len) { | |
| if (hex_str_len < uVar4 + 2) { | |
| assert_fail(uVar4,uVar4 + 2,hex_str_len, | |
| &PTR_s_wasm/src/launch_signature.rs_ram_00101889_ram_00102788); | |
| do { | |
| halt_trap(); | |
| } while( true ); | |
| } | |
| uVar8 = (uint)(*(char *)(hex_str + uVar4) == '+'); | |
| pbVar5 = (byte *)(hex_str + uVar4 + uVar8); | |
| uVar6 = (uint)*pbVar5; | |
| uVar7 = (uVar6 - 0x41 & 0xffffffdf) + 10; | |
| if (uVar6 < 0x3a) { | |
| uVar7 = uVar6 - 0x30; | |
| } | |
| if (uVar7 < 0x10) { | |
| if (uVar8 == 0) { | |
| uVar8 = (uint)pbVar5[1]; | |
| uVar6 = (uVar8 - 0x41 & 0xffffffdf) + 10; | |
| if (uVar8 < 0x3a) { | |
| uVar6 = uVar8 - 0x30; | |
| } | |
| if (0xf < uVar6) goto code_r0x8004190e; | |
| uVar7 = uVar7 << 4 | uVar6; | |
| } | |
| if (local_18 == dst) { | |
| unnamed_function_502(&local_18); | |
| } | |
| *(byte *)(local_14 + dst) = (byte)uVar7 ^ 0x73; | |
| dst = dst + 1; | |
| local_10 = dst; | |
| } | |
| } | |
| code_r0x8004190e: | |
| uVar4 = uVar4 + 2; | |
| local_4c = local_14; | |
| } | |
| uVar4 = 0; | |
| local_48 = dst; | |
| unnamed_function_217(&local_30,&local_4c); | |
| size = local_2c; | |
| src = local_30; | |
| uVar7 = local_3c; | |
| if (local_30 == 0) { | |
| dst_0 = 0; | |
| local_c = 0x80000000; | |
| str_0 = 1; | |
| } | |
| else if (local_24 == 0) { | |
| dst_0 = local_2c; | |
| local_c = 0x80000000; | |
| str_0 = local_30; | |
| } | |
| else { | |
| if (dst == 0) { | |
| local_40 = 1; | |
| } | |
| else { | |
| local_40 = unnamed_function_955(dst,1); | |
| if (local_40 == 0) { | |
| panic_capacity_overflow(1,dst); | |
| do { | |
| halt_trap(); | |
| } while( true ); | |
| } | |
| } | |
| local_3c = 0; | |
| local_44 = dst; | |
| if (dst < size) { | |
| unnamed_function_449(&local_44,0,size); | |
| uVar4 = local_3c; | |
| } | |
| if (size != 0) { | |
| memory_copy(0,0,size,src,local_40 + uVar4); | |
| } | |
| local_3c = size + uVar4; | |
| if (local_44 - local_3c < 3) { | |
| unnamed_function_449(&local_44,local_3c,3); | |
| } | |
| uVar2 = DAT_ram_0012a3ec; | |
| *(undefined2 *)(local_3c + local_40) = DAT_ram_0012a3ec; | |
| uVar3 = DAT_ram_0012a3ee; | |
| *(undefined1 *)((undefined2 *)(local_3c + local_40) + 1) = DAT_ram_0012a3ee; | |
| size = local_3c + 3; | |
| local_38 = CONCAT44(local_48,local_4c); | |
| local_3c = size; | |
| unnamed_function_217(&local_30,&local_38); | |
| dst_0 = local_3c; | |
| src = local_30; | |
| dst = local_2c; | |
| iVar1 = local_24; | |
| while (local_3c = size, uVar7 = dst_0, local_c = local_44, str_0 = local_40, src != 0) { | |
| local_30 = src; | |
| local_2c = dst; | |
| local_24 = iVar1; | |
| if (local_44 - local_3c < dst) { | |
| size = local_3c; | |
| local_3c = dst_0; | |
| unnamed_function_449(&local_44,size,dst); | |
| } | |
| if (dst != 0) { | |
| memory_copy(0,0,dst,src,local_3c + local_40); | |
| } | |
| size = local_3c + dst; | |
| if (iVar1 != 0) { | |
| if (local_44 - size < 3) { | |
| local_3c = size; | |
| unnamed_function_449(&local_44,size,3); | |
| size = local_3c; | |
| } | |
| *(undefined2 *)(size + local_40) = uVar2; | |
| *(undefined1 *)((undefined2 *)(size + local_40) + 1) = uVar3; | |
| size = size + 3; | |
| } | |
| local_3c = size; | |
| unnamed_function_217(&local_30,&local_38); | |
| dst_0 = local_3c; | |
| src = local_30; | |
| dst = local_2c; | |
| iVar1 = local_24; | |
| } | |
| } | |
| local_3c = uVar7; | |
| dst = dst_0; | |
| src = str_0; | |
| if (dst_0 == 0) { | |
| size = 1; | |
| } | |
| else { | |
| size = unnamed_function_955(dst_0,1); | |
| if (size == 0) { | |
| panic_capacity_overflow(1,dst); | |
| do { | |
| halt_trap(); | |
| } while( true ); | |
| } | |
| } | |
| if (dst != 0) { | |
| memory_copy(0,0,dst,src,size); | |
| } | |
| ret_data->dest_0 = dst; | |
| ret_data->size = size; | |
| ret_data->dest = dst; | |
| if ((local_c | 0x80000000) != 0x80000000) { | |
| dlmalloc::validate_size(src,local_c,1); | |
| } | |
| if (local_18 != 0) { | |
| dlmalloc::validate_size(local_14,local_18,1); | |
| } | |
| } | |
| else { | |
| ret_data->dest_0 = 0; | |
| ret_data->dest = 0; | |
| ret_data->size = 1; | |
| } | |
| return; | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This was made by just copy pasting the cpp and the strings over to chatgpt to get it to rewrite it, this gives a general overview on how it functions but you may want to verify it's contents | |
| // Closest “shape” to what this was in Rust (wasm-bindgen + js-sys style). | |
| // Notes: | |
| // - The C-like output is from wasm glue + Rust monomorphization, so names/handles differ. | |
| // - This keeps the same control flow: decode hex -> XOR 0x73 -> Reflect::get -> is_undefined. | |
| // - Bit indexing matches the decomp: bit order is reversed within each byte. | |
| // | |
| // Cargo features you likely had: | |
| // wasm-bindgen = "0.2" | |
| // js-sys = "0.3" | |
| // web-sys = { version = "0.3", features = ["Crypto", "Window"] } | |
| use js_sys::{Reflect, Uint8Array, Object}; | |
| use wasm_bindgen::prelude::*; | |
| use wasm_bindgen::JsValue; | |
| const XOR_KEY: u8 = 0x73; | |
| /// Decode the obfuscated hex used in your listing: | |
| /// - reads pairs of hex chars | |
| /// - allows a leading '+' before the first nibble (the decomp checked for '+') | |
| /// - XORs each decoded byte with 0x73 | |
| /// - returns UTF-8 string (lossy in case something weird slips in) | |
| fn decode_hex_xor73(hex: &str) -> String { | |
| let bytes = hex.as_bytes(); | |
| if bytes.len() % 2 != 0 { | |
| // matches the decomp behavior: odd-length => return empty-ish | |
| return String::new(); | |
| } | |
| fn nibble(b: u8) -> Option<u8> { | |
| match b { | |
| b'0'..=b'9' => Some(b - b'0'), | |
| b'a'..=b'f' => Some((b - b'a') + 10), | |
| b'A'..=b'F' => Some((b - b'A') + 10), | |
| _ => None, | |
| } | |
| } | |
| let mut out = Vec::with_capacity(bytes.len() / 2); | |
| let mut i = 0; | |
| while i + 1 < bytes.len() { | |
| // The decomp did: | |
| // uVar8 = (*(char *)(hex_str + uVar4) == '+'); | |
| // pb = hex_str + uVar4 + uVar8; | |
| // So allow '+' for the first nibble position of the pair. | |
| let mut j = i; | |
| if bytes[j] == b'+' { | |
| j += 1; | |
| if j >= bytes.len() { | |
| break; | |
| } | |
| } | |
| let hi = match nibble(bytes[j]) { | |
| Some(v) => v, | |
| None => { | |
| i += 2; | |
| continue; | |
| } | |
| }; | |
| // If we had '+', the second nibble is the next byte after hi. | |
| // If not, it’s i+1 as usual. | |
| let lo_index = if j == i { i + 1 } else { j + 1 }; | |
| if lo_index >= bytes.len() { | |
| break; | |
| } | |
| let lo = match nibble(bytes[lo_index]) { | |
| Some(v) => v, | |
| None => { | |
| i += 2; | |
| continue; | |
| } | |
| }; | |
| let b = ((hi << 4) | lo) ^ XOR_KEY; | |
| out.push(b); | |
| i += 2; | |
| } | |
| String::from_utf8_lossy(&out).into_owned() | |
| } | |
| /// Reverse-bit index inside each byte exactly like: | |
| /// byte_index = id >> 3 | |
| /// bit_in_byte = (id ^ 0xffffffff) & 7 | |
| /// overall_bit = byte_index*8 + bit_in_byte | |
| #[inline] | |
| fn bit_index_from_clientmod_id(clientmod_id: u32) -> u32 { | |
| let byte_base = clientmod_id & !7; | |
| let bit_in_byte = 7 - (clientmod_id & 7); | |
| byte_base + bit_in_byte | |
| } | |
| /// Check a list of property names; if any exists (non-undefined), set the flag bit. | |
| /// If none exists, clear the flag bit. | |
| fn detect_client_and_set_flag( | |
| bitfield: &mut u64, | |
| clientmod_id: u32, | |
| js_context: &JsValue, | |
| obf_hex_keys: &[&'static str], | |
| ) { | |
| // In the decomp: | |
| // remaining = totalStrs << 3; each loop subtracts 8; so it's just "for each string". | |
| let mut detected = false; | |
| for hex in obf_hex_keys { | |
| let key = decode_hex_xor73(hex); | |
| // js_get_property_checked(&status_value, js_context, &key) | |
| // Here: Reflect::get(ctx, &JsValue::from_str(&key)) | |
| let v = Reflect::get(js_context, &JsValue::from_str(&key)) | |
| .unwrap_or(JsValue::UNDEFINED); | |
| // decomp logic: | |
| // if ((status_value & 1) == 0) { bVar1 = isUndefined(local_14) == 1; } | |
| // else { bVar1 = true; } | |
| // | |
| // Interpreting that with js-sys: loop continues while undefined, | |
| // exits (and sets bit) when value is NOT undefined. | |
| if !v.is_undefined() { | |
| detected = true; | |
| break; | |
| } | |
| } | |
| let bit = bit_index_from_clientmod_id(clientmod_id); | |
| let mask = 1u64 << bit; | |
| if detected { | |
| *bitfield |= mask; | |
| } else { | |
| *bitfield &= !mask; | |
| } | |
| } | |
| // ---- Obfuscated strings you provided ---- | |
| // jQuery-ish checks | |
| const JQUERY_HEX: &str = "19220616010a"; // jQuery | |
| const DOLLAR_HEX: &str = "57"; // $ | |
| const FN_HEX: &str = "151d"; // fn | |
| const JQUERY2_HEX: &str = "19020616010a"; // jquery | |
| // Client detection keys (each entry is a property name to probe on the JS global) | |
| const BETTERDISCORD_KEYS: &[&str] = &[ | |
| "311607071601371a00101c0117", // BetterDiscord | |
| "311607071601371a00101c01172301161f1c1217", // BetterDiscordPreload | |
| "311732031a", // BdApi | |
| ]; | |
| const RAMBOX_KEYS: &[&str] = &[ | |
| "01121e111c0b", // rambox | |
| ]; | |
| const REVENGE_KEYS: &[&str] = &[ | |
| "011605161d1416", // revenge | |
| "05161d1716070712", // vendetta | |
| "11061d1d0a", // bunny | |
| "1816070706", // kettu | |
| ]; | |
| const VENCORD_KEYS: &[&str] = &[ | |
| "25161d101c0117", // Vencord | |
| "25161d101c01173d12071a0516", // VencordNative | |
| "25160018071c033d12071a0516", // VesktopNative | |
| "25161d101c01173e1c111a1f16", // VencordMobile | |
| ]; | |
| const REPLUGGED_KEYS: &[&str] = &[ | |
| "0116031f0614141617", // replugged | |
| "2116031f06141416173d12071a0516", // RepluggedNative | |
| ]; | |
| const LEGCORD_KEYS: &[&str] = &[ | |
| "1f1614101c0117", // legcord | |
| "3f1614101c0117212330", // LegcordRPC | |
| ]; | |
| const DORION_KEYS: &[&str] = &[ | |
| "371c011a1c1d", // Dorion | |
| "2c2c373c213a3c3d2c303c3d353a342c2c", // __DORION_CONFIG__ | |
| "2c2c373c213a3c3d2c3a3d3a272c2c", // __DORION_INIT__ | |
| ]; | |
| const GCDP_KEYS: &[&str] = &[ | |
| "34303723", // GCDP | |
| ]; | |
| const OPENASAR_KEYS: &[&str] = &[ | |
| "1c03161d12001201", // openasar | |
| ]; | |
| const SHELTER_KEYS: &[&str] = &[ | |
| "001b161f071601", // shelter | |
| ]; | |
| const MOONLIGHT_KEYS: &[&str] = &[ | |
| "1e1c1c1d1f1a141b07", // moonlight | |
| "1e1c1c1d1f1a141b073d1c1716", // moonlightNode | |
| ]; | |
| // ---- “launch signature” generator ---- | |
| fn get_random_16() -> [u8; 16] { | |
| let mut buf = [0u8; 16]; | |
| // Equivalent to __wbg_getRandomValues(..., 0x10) | |
| // Prefer web_sys::window().crypto(); fall back to js_sys::global crypto if needed. | |
| #[cfg(feature = "web-sys")] | |
| { | |
| if let Some(w) = web_sys::window() { | |
| if let Ok(crypto) = w.crypto() { | |
| // get_random_values_with_u8_array exists on web-sys Crypto | |
| let _ = crypto.get_random_values_with_u8_array(&mut buf); | |
| return buf; | |
| } | |
| } | |
| } | |
| // Generic fallback: (globalThis.crypto.getRandomValues) | |
| let global = js_sys::global(); | |
| let crypto = Reflect::get(&global, &JsValue::from_str("crypto")) | |
| .unwrap_or(JsValue::UNDEFINED); | |
| if !crypto.is_undefined() { | |
| let arr = Uint8Array::new_with_length(16); | |
| let _ = Reflect::get(&crypto, &JsValue::from_str("getRandomValues")) | |
| .ok() | |
| .and_then(|f| f.dyn_into::<js_sys::Function>().ok()) | |
| .map(|f| f.call1(&crypto, &arr.into())); | |
| arr.copy_to(&mut buf); | |
| } | |
| buf | |
| } | |
| fn uuid_like_from_bytes(b: [u8; 16]) -> String { | |
| // standard 8-4-4-4-12 hex with hyphens, lowercase | |
| let mut s = String::with_capacity(36); | |
| for (i, byte) in b.iter().enumerate() { | |
| if matches!(i, 4 | 6 | 8 | 10) { | |
| s.push('-'); | |
| } | |
| s.push_str(&format!("{:02x}", byte)); | |
| } | |
| s | |
| } | |
| fn has_defined_property(obj: &JsValue, name: &str) -> bool { | |
| Reflect::get(obj, &JsValue::from_str(name)) | |
| .map(|v| !v.is_undefined() && !v.is_null()) | |
| .unwrap_or(false) | |
| } | |
| #[wasm_bindgen] | |
| pub fn generate_launch_signature() -> String { | |
| let js_context = js_sys::global(); | |
| // random UUID-ish 16 bytes, then enforce version/variant bits | |
| // The decomp’s bit-twiddling corresponds to: | |
| // - version 4 in the "time_hi_and_version" nibble | |
| // - RFC4122 variant in the "clock_seq_hi_and_reserved" bits | |
| let mut rnd = get_random_16(); | |
| rnd[6] = (rnd[6] & 0x0f) | 0x40; // version 4 | |
| rnd[8] = (rnd[8] & 0x3f) | 0x80; // variant RFC4122 | |
| // The decomp also did a “jQuery present?” test and toggled 0x8000. | |
| // This is the closest read of that control flow: | |
| // | |
| // - If globalThis["jQuery"] exists and is not null/undefined => set 0x8000 | |
| // - Else if globalThis["$"] exists and is not null/undefined, then check "$.fn" | |
| // and if that looks valid, clear 0x8000; otherwise set it. | |
| let jquery_key = decode_hex_xor73(JQUERY_HEX); | |
| let dollar_key = decode_hex_xor73(DOLLAR_HEX); | |
| let fn_key = decode_hex_xor73(FN_HEX); | |
| let jquery2_key = decode_hex_xor73(JQUERY2_HEX); | |
| let mut client_bitfield: u64 = 0; | |
| let jquery_obj = Reflect::get(&js_context, &JsValue::from_str(&jquery_key)) | |
| .unwrap_or(JsValue::UNDEFINED); | |
| if !jquery_obj.is_undefined() && !jquery_obj.is_null() { | |
| client_bitfield |= 0x8000; | |
| } else { | |
| // Try "$" | |
| let dollar_obj = Reflect::get(&js_context, &JsValue::from_str(&dollar_key)) | |
| .unwrap_or(JsValue::UNDEFINED); | |
| if !dollar_obj.is_undefined() && !dollar_obj.is_null() { | |
| // If "$.fn" looks usable, clear; otherwise set. | |
| let fn_ok = has_defined_property(&dollar_obj, &fn_key); | |
| if !fn_ok { | |
| client_bitfield |= 0x8000; | |
| } else { | |
| client_bitfield &= !0x8000; | |
| } | |
| } else { | |
| // Check for lowercase jquery | |
| if has_defined_property(&js_context, &jquery2_key) { | |
| client_bitfield |= 0x8000; | |
| } else { | |
| client_bitfield &= !0x8000; | |
| } | |
| } | |
| } | |
| // Client mod probes (ids match the calls in the decomp) | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0001_0011, &js_context, BETTERDISCORD_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0001_1011, &js_context, RAMBOX_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0010_0100, &js_context, REVENGE_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0010_1011, &js_context, VENCORD_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0011_0100, &js_context, REPLUGGED_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0100_0010, &js_context, LEGCORD_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0100_1000, &js_context, DORION_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0100_1111, &js_context, GCDP_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0101_1001, &js_context, OPENASAR_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0110_0111, &js_context, SHELTER_KEYS); | |
| detect_client_and_set_flag(&mut client_bitfield, 0b0111_0100, &js_context, MOONLIGHT_KEYS); | |
| // The decomp strongly suggests they *embed* the client bitfield into the first 8 bytes, | |
| // then use the random UUID bytes for the remaining 8 bytes, and finally print as UUID. | |
| // | |
| // That explains why it converts `bitfield` to hex nibbles first, then `local_68`. | |
| let mut sig_bytes = [0u8; 16]; | |
| sig_bytes[..8].copy_from_slice(&client_bitfield.to_le_bytes()); | |
| sig_bytes[8..].copy_from_slice(&rnd[8..16]); | |
| uuid_like_from_bytes(sig_bytes) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| "jQuery" | |
| "19220616010a" | |
| "$" | |
| "57" | |
| "fn" | |
| "151d" | |
| "jquery" | |
| "19020616010a" | |
| "BetterDiscord" | |
| "311607071601371a00101c0117" | |
| "BetterDiscordPreload" | |
| "311607071601371a00101c01172301161f1c1217" | |
| "BdApi" | |
| "311732031a" | |
| "rambox" | |
| "01121e111c0b" | |
| "revenge" | |
| "011605161d1416" | |
| "vendetta" | |
| "05161d1716070712" | |
| "bunny" | |
| "11061d1d0a" | |
| "kettu" | |
| "1816070706" | |
| "Vencord" | |
| "25161d101c0117" | |
| "VencordNative" | |
| "25161d101c01173d12071a0516" | |
| "VesktopNative" | |
| "25160018071c033d12071a0516" | |
| "VencordMobile" | |
| "25161d101c01173e1c111a1f16" | |
| "replugged" | |
| "0116031f0614141617" | |
| "RepluggedNative" | |
| "2116031f06141416173d12071a0516" | |
| "legcord" | |
| "1f1614101c0117" | |
| "LegcordRPC" | |
| "3f1614101c0117212330" | |
| "Dorion" | |
| "371c011a1c1d" | |
| "__DORION_CONFIG__" | |
| "2c2c373c213a3c3d2c303c3d353a342c2c" | |
| "__DORION_INIT__" | |
| "2c2c373c213a3c3d2c3a3d3a272c2c" | |
| "GCDP" | |
| "34303723" | |
| "openasar" | |
| "1c03161d12001201" | |
| "shelter" | |
| "001b161f071601" | |
| "moonlight" | |
| "1e1c1c1d1f1a141b07" | |
| "moonlightNode" | |
| "1e1c1c1d1f1a141b073d1c1716" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment