└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # MapleStory v95 Client Analysis 2 | 3 | Hello everyone welcome to our analysis on GMS v95.1 4 | - This document is a work in progress 5 | - This is not a professional document 6 | - The primary focus is on what I d to do to create localhost 7 | - There is too much for me to go into excruciating detail about 8 | - Please contribute if you know anything more !!! 9 | 10 | 11 | ## CSecurityClient 12 | Class used to handle anti cheat integration 13 | - Houses HackShield related fields in this version 14 | - In other versions houses NGS and XignCode3 relations fields 15 | - Is a TSingleton 16 | 17 | In lots of places in the client usage of CSecurityClient looks like such (pseudo): 18 | ```cpp 19 | 20 | if ( TSingleton::IsInstantiated() ) 21 | { 22 | TSingleton::GetInstance(); 23 | 24 | //Usage of class such as 25 | CSecurityClient::InitModule(); 26 | } 27 | 28 | ``` 29 | PatchRetZero here can save you lots of patches you'd have to do in other places otherwise. 30 | ##### Class Pseudo 31 | 32 | ```cpp 33 | // write access to const memory has been detected, the output may be wrong! 34 | void __thiscall CSecurityClient::CSecurityClient(CSecurityClient *this) 35 | { 36 | CSecurityClient *v1; // edi 37 | TSecType *v2; // esi 38 | int v3; // eax 39 | char v4; // dl 40 | TSecData *v5; // ecx 41 | int v6; // eax 42 | TSecData *v7; // edx 43 | int v8; // eax 44 | char v9; // cl 45 | 46 | v1 = this; 47 | v2 = &this->m_bInitModule; 48 | if ( this == (CSecurityClient *)-4 ) 49 | TSingleton::ms_pInstance = 0; 50 | else 51 | TSingleton::ms_pInstance = this; 52 | this->vfptr = (CSecurityClientVtbl *)&CSecurityClient::`vftable'; 53 | this->m_bInitModule.m_secdata = (TSecData *)ZAllocEx::Alloc( 54 | &ZAllocEx::_s_alloc, 55 | 0xCu); 56 | v2->FakePtr1 = (unsigned int)&v2[-1365].FakePtr2 + rand(); 57 | v3 = rand(); 58 | v4 = v2->FakePtr1; 59 | v5 = v2->m_secdata; 60 | v2->FakePtr2 = (unsigned int)&v2[-1365].FakePtr2 + v3; 61 | v5->FakePtr1 = v4; 62 | v2->m_secdata->FakePtr2 = v2->FakePtr2; 63 | TSecType::SetData(v2, 0); 64 | v1->m_bStartModule.m_secdata = (TSecData *)ZAllocEx::Alloc( 65 | &ZAllocEx::_s_alloc, 66 | 0xCu); 67 | v1->m_bStartModule.FakePtr1 = (unsigned int)&v1[-52].m_szHShieldPath[rand() + 20]; 68 | v6 = rand(); 69 | v7 = v1->m_bStartModule.m_secdata; 70 | v1->m_bStartModule.FakePtr2 = (unsigned int)&v1[-52].m_szHShieldPath[v6 + 20]; 71 | v7->FakePtr1 = v1->m_bStartModule.FakePtr1; 72 | v1->m_bStartModule.m_secdata->FakePtr2 = v1->m_bStartModule.FakePtr2; 73 | TSecType::SetData(&v1->m_bStartModule, 0); 74 | v1->m_nThreatCode = 0; 75 | v1->m_nThreatParamSize.m_secdata = (TSecData *)ZAllocEx::Alloc( 76 | &ZAllocEx::_s_alloc, 77 | 0xCu); 78 | v1->m_nThreatParamSize.FakePtr1 = (unsigned int)&v1[-52].m_szHShieldPath[rand() + 36]; 79 | v8 = rand(); 80 | v9 = v1->m_nThreatParamSize.FakePtr1; 81 | v1->m_nThreatParamSize.FakePtr2 = (unsigned int)&v1[-52].m_szHShieldPath[v8 + 36]; 82 | v1->m_nThreatParamSize.m_secdata->FakePtr1 = v9; 83 | v1->m_nThreatParamSize.m_secdata->FakePtr2 = v1->m_nThreatParamSize.FakePtr2; 84 | TSecType::SetData(&v1->m_nThreatParamSize, 0); 85 | v1->m_pThreatParam = 0; 86 | v1->m_hMainWnd = 0; 87 | } 88 | void __thiscall CSecurityClient::InitModule(CSecurityClient *this) 89 | { 90 | CSecurityClient *v1; // esi 91 | unsigned int v2; // eax 92 | int v3; // eax 93 | int (__stdcall **pExceptionObject)(ZXString *); // [esp+4h] [ebp-214h] 94 | unsigned int v5; // [esp+8h] [ebp-210h] 95 | CHAR sModulePath; // [esp+Ch] [ebp-20Ch] 96 | char v7; // [esp+Dh] [ebp-20Bh] 97 | unsigned __int8 sModuleFolderPath; // [esp+110h] [ebp-108h] 98 | char v9; // [esp+111h] [ebp-107h] 99 | 100 | v1 = this; 101 | sModuleFolderPath = 0; 102 | memset(&v9, 0, 0x103u); 103 | sModulePath = 0; 104 | memset(&v7, 0, 0x103u); 105 | GetModuleFolderName((char *)&sModuleFolderPath); 106 | _mbsnbcpy((unsigned __int8 *)&sModulePath, &sModuleFolderPath, 0x104u); 107 | _mbsnbcat((unsigned __int8 *)&sModulePath, "\\HShield", 8u); 108 | _mbsnbcpy((unsigned __int8 *)v1->m_szHShieldPath, (const unsigned __int8 *)&sModulePath, 0x104u); 109 | v2 = _AhnHS_HSUpdateA(&sModulePath, 600000u, 20000u); 110 | if ( v2 ) 111 | { 112 | v5 = v2; 113 | pExceptionObject = CSecurityUpdateFailed::`vftable'; 114 | _CxxThrowException(&pExceptionObject, &_TI2_AVCSecurityUpdateFailed__); 115 | } 116 | _mbsnbcpy((unsigned __int8 *)&sModulePath, &sModuleFolderPath, 0x104u); 117 | _mbsnbcat((unsigned __int8 *)&sModulePath, "\\HShield\\EHSvc.dll", 0x12u); 118 | v3 = _AhnHS_InitializeA(&sModulePath, (int)_AhnHS_Callback, 9947, (int)"B7621D704ED72C489EE54605", 46808511, 1); 119 | if ( v3 ) 120 | { 121 | v5 = v3; 122 | pExceptionObject = CSecurityInitFailed::`vftable'; 123 | _CxxThrowException(&pExceptionObject, &_TI2_AVCSecurityInitFailed__); 124 | } 125 | TSecType::SetData(&v1->m_bInitModule, 1); 126 | } 127 | void __thiscall CSecurityClient::ClearModule(CSecurityClient *this) 128 | { 129 | TSecType *v1; // esi 130 | signed int v2; // eax 131 | int (__stdcall **pExceptionObject)(ZXString *); // [esp+4h] [ebp-8h] 132 | int v4; // [esp+8h] [ebp-4h] 133 | 134 | v1 = &this->m_bInitModule; 135 | if ( TSecType::GetData(&this->m_bInitModule) ) 136 | { 137 | v2 = _AhnHS_Uninitialize(); 138 | if ( v2 ) 139 | { 140 | v4 = v2; 141 | pExceptionObject = CSecurityClearFailed::`vftable'; 142 | _CxxThrowException(&pExceptionObject, &_TI2_AVCSecurityClearFailed__); 143 | } 144 | TSecType::SetData(v1, 0); 145 | } 146 | } 147 | void __thiscall CSecurityClient::StartModule(CSecurityClient *this) 148 | { 149 | CSecurityClient *v1; // esi 150 | signed int v2; // eax 151 | int (__stdcall **v3)(ZXString *); // [esp+0h] [ebp-Ch] 152 | int v4; // [esp+4h] [ebp-8h] 153 | 154 | v1 = this; 155 | v2 = _AhnHS_StartService(); 156 | if ( v2 ) 157 | { 158 | v4 = v2; 159 | v3 = CSecurityInitFailed::`vftable'; 160 | _CxxThrowException(&v3, &_TI2_AVCSecurityInitFailed__); 161 | } 162 | _AhnHS_CheckHackShieldRunningStatus(); 163 | v1->m_dwCallbackTime = GetTickCount(); 164 | TSecType::SetData(&v1->m_bStartModule, 1); 165 | } 166 | void __thiscall CSecurityClient::StopModule(CSecurityClient *this) 167 | { 168 | TSecType *v1; // esi 169 | signed int v2; // eax 170 | int (__stdcall **pExceptionObject)(ZXString *); // [esp+4h] [ebp-8h] 171 | int v4; // [esp+8h] [ebp-4h] 172 | 173 | v1 = &this->m_bStartModule; 174 | if ( TSecType::GetData(&this->m_bStartModule) ) 175 | { 176 | v2 = _AhnHS_StopService(); 177 | if ( v2 ) 178 | { 179 | v4 = v2; 180 | pExceptionObject = CSecurityClearFailed::`vftable'; 181 | _CxxThrowException(&pExceptionObject, &_TI2_AVCSecurityClearFailed__); 182 | } 183 | TSecType::SetData(v1, 0); 184 | } 185 | } 186 | 187 | //Just throws an exception if HS error code is set 188 | //Checks CSecurityClient->m_nThreatCode is a bad HS return code and throw ( result > 0x10501 ) 189 | signed int __thiscall CSecurityClient__Update(_DWORD *this) 190 | { 191 | signed int result; // eax 192 | bool v2; // zf 193 | bool v3; // sf 194 | unsigned __int8 v4; // of 195 | int (__stdcall **v5)(int); // [esp+0h] [ebp-8h] 196 | int v6; // [esp+4h] [ebp-4h] 197 | 198 | result = this[7]; 199 | if ( result > 0x10501 ) 200 | { 201 | if ( result > 0x10801 ) 202 | { 203 | if ( result != 0x10A01 ) 204 | return result; 205 | LABEL_18: 206 | v6 = this[7]; 207 | v5 = &off_BF643C; 208 | sub_A68B61((int)&v5, &_TI2_AVCSecurityThreatDetected__); 209 | JUMPOUT(*(_DWORD *)algn_A52B42); 210 | } 211 | if ( result == 0x10801 || result == 67073 ) 212 | goto LABEL_18; 213 | if ( result <= 0x10700 ) 214 | return result; 215 | v4 = __OFSUB__(result, 67333); 216 | v2 = result == 67333; 217 | v3 = result - 67333 < 0; 218 | LABEL_10: 219 | if ( !((unsigned __int8)(v3 ^ v4) | v2) ) 220 | return result; 221 | goto LABEL_18; 222 | } 223 | if ( result == 0x10501 ) 224 | goto LABEL_18; 225 | if ( result > 0x10303 ) 226 | { 227 | if ( result < 0x10306 ) 228 | return result; 229 | v4 = __OFSUB__(result, 66312); 230 | v2 = result == 66312; 231 | v3 = result - 66312 < 0; 232 | goto LABEL_10; 233 | } 234 | if ( result >= 0x10301 || result == 0x10102 || result == 0x10104 ) 235 | goto LABEL_18; 236 | return result; 237 | } 238 | ``` 239 | 240 | ## Other Weird Checks 241 | 242 | - Game checks ` ws2_32.dll ` dos header magic to see if its been tampered 243 | - Game removes loopback adapters ( TODO: Add the winapi call name here ) 244 | - ` GetIpAddrTable GetAdaptersInfo ` calls used to check adapter stuff ? 245 | - Client checks for the HackShield mutex ` meteora ` 246 | - Client checks to see if ` ehsvc.dll ` is loaded 247 | - Client literally does an IAT count on the ` ehsvc.dll ` to see if its been tampered. I just loaded original 248 | - Client checks ` WvsClientMutex ` mutex for multi client 249 | 250 | ## IP Checks 251 | 252 | - Game is fucking booby trapped with IP checks 253 | - It's not worth me pointing out where ( will eventually ) 254 | - But basically getpeername is called, just return the expected IP ` 63.251.217.1 ` 255 | - Sad thing is they have heavy API checks on winsock so use the WSP variants like I do 256 | - TODO: Talk more about the ` MyGetProcAddress ` and heavy winapi checks ( xxxx.nst ) 257 | 258 | ## CWvsApp Checks 259 | - ` CSecurityClient::Update ` is called in ` CWvsApp::Run ` 260 | - ` CWvsApp->m_tLastServerIPCheck ` is in ` CWvsApp::CallUpdate ` 261 | - ` CWvsApp->m_tLastServerIPCheck2 ` is in ` CWvsApp::Run ` | Also contains CSecurityClient check below 262 | - ` CWvsApp->m_tLastSecurityCheck ` is in ` CWvsApp::Run ` 263 | 264 | #### CWvsApp->m_tLastSecurityCheck 265 | Everyday I pray and ask god what this done but I am unsure. All i know is i have to spoof it so client doesn't crash. 266 | 267 | #### CSecurityClient Check 268 | Thisis inside m_tLastServerIPCheck2 269 | Checks if some files in the HShield folder exist `3N.mhe, v3warpds.v3d, v3warpns.v3d ` 270 | Checks ` _AhnHS_StartSerice ` ret and expects `HS_ERR_ALREADY_SERVICE_RUNNING` ( 0x00000201 ) 271 | Checks `CSecurityClient->m_dwCallbackTime` is ` <= 60000 ` 272 | 273 | ```cpp 274 | if ( TSingleton_CSecurityClient__IsInstantiated() ) 275 | { 276 | v22 = '\x01'; 277 | v15 = '3'; 278 | v16 = 'N'; 279 | v17 = '.'; 280 | v18 = 'm'; 281 | v19 = 'h'; 282 | v20 = 'e'; 283 | v21 = '\0'; 284 | v25 = 'v'; 285 | v26 = '3'; 286 | v27 = 'w'; 287 | v28 = 'a'; 288 | v29 = 'r'; 289 | v30 = 'p'; 290 | v31 = 'd'; 291 | v32 = 's'; 292 | v33 = '.'; 293 | v34 = 'v'; 294 | v35 = '3'; 295 | v36 = 'd'; 296 | v37 = '\0'; 297 | v10 = TSingleton_CSecurityClient__GetInstance(); 298 | sub_A6A463(&FileName, "%s\\%s", v10 + 52); 299 | hObject = CreateFileA(&FileName, 0x40000000u, 0, 0, 3u, 0, 0); 300 | if ( GetLastError() != 32 ) 301 | v22 = 0; 302 | if ( hObject != (HANDLE)-1 ) 303 | CloseHandle(hObject); 304 | if ( _AhnHS_StartService() != 513 ) 305 | v22 = 0; 306 | v11 = GetTickCount(); 307 | if ( v11 - *(_DWORD *)(TSingleton_CSecurityClient__GetInstance() + 48) > 60000 ) 308 | v22 = 0; 309 | } 310 | ``` 311 | Relevant HS callback to above 312 | ```cpp 313 | 314 | int __stdcall _AhnHS_Callback(int lCode, int lParamSize, void *pParam) 315 | { 316 | if ( lCode == 65537 ) 317 | { 318 | if ( TSingleton::ms_pInstance ) 319 | { 320 | TSingleton::ms_pInstance->m_dwCallbackTime = GetTickCount(); 321 | return 0; 322 | } 323 | } 324 | else if ( TSingleton::ms_pInstance ) 325 | { 326 | TSingleton::ms_pInstance->m_nThreatCode = lCode; 327 | TSecType::SetData(&TSingleton::ms_pInstance->m_nThreatParamSize, lParamSize); 328 | TSingleton::ms_pInstance->m_pThreatParam = pParam; 329 | } 330 | return 0; 331 | } 332 | 333 | ``` 334 | 335 | ## MSCRC 336 | - ~~I am still trying to figure this out. Please contribute if you know !!!~~ 337 | - ~~Crc32__GetCrc32~~ 338 | - ~~Crc32__GetCrc32_VMCRC~~ 339 | - ~~Crc32__GetCrc32_VMTABLE~~ 340 | 341 | - Click [here](https://github.com/lastbattle/Harepacker-resurrected/commit/814ed0b6b549e7550df17b5a6f5ba8db6964adc5) to view the rebuilt functions. Contribution and special thanks to LastBattle. 342 | 343 | ## CWvsContext::OnEnterField 344 | Ignore this super shitty pseudo analysis below until I actually solve it. PatchRetZero to skip the call. This MSCRC bypass still used in v200 GMS today. However it skips some game code we need actually need !!! ( Closing UI's and other shit ) 345 | 346 | ``` 347 | //Three VM sections in here 348 | void CWvsContext::OnEnterField() 349 | { 350 | //BlaBlaBla 351 | 352 | CWvsContext::UI_CloseRevive() 353 | 354 | BEGIN_VM_BLOCK 355 | 356 | bAuth is a parameter/ret in a MSCRC function (?) 357 | 358 | bAuth = 0 359 | var24 = 0 360 | var28 = CClientSocket::SendPacket 361 | 362 | Check first byte of CClientSocket::SendPacket against: 363 | (0x55 or 0xB8 or 0x6A ) 364 | 365 | If check fails: CLIENT_BLOWUP_DEATH 366 | 367 | 368 | //This mov may have been insert manually 369 | _text:009DBF79 058 C7 45 E8 15 08 45 19 mov [ebp+dwThemidaCheckValue], 19450815h 370 | 371 | NOPPED CODE I BELIEVE TO THE MSCRC 372 | 373 | Compare ebp_dwThemidaCheckValue to the hardcoded value 374 | 375 | If check fails: CLIENT_BLOWUP_DEATH 376 | 377 | END_VM_BLOCK 378 | 379 | CTemporaryStatView::Show(void) 380 | //BlaBlaBla 381 | TSingleton::GetInstance(void) 382 | 383 | BEGIN_VM_BLOCK 384 | Nopped Shit I need to RE 385 | END_VM_BLOCK 386 | 387 | TSingleton::IsInstantiated(void) 388 | //BlaBlaBla 389 | CField::IsSwimmingMap() 390 | 391 | BEGIN_VM_BLOCK 392 | Check value of ` bAuth ` 393 | If check fails: CLIENT_BLOWUP_DEATH 394 | END_VM_BLOCK 395 | 396 | 397 | CTemporaryStatView::Show(void) 398 | //BlaBlaBla 399 | CConfig::SaveSessionInfo_FieldID() 400 | } 401 | ==================================================== 402 | CLIENT_BLOWUP_DEATH: 403 | _text:009DBF53 058 31 DB xor ebx, ebx 404 | _text:009DBF55 058 31 D2 xor edx, edx 405 | _text:009DBF57 058 31 F6 xor esi, esi 406 | _text:009DBF59 058 31 FF xor edi, edi 407 | _text:009DBF5B 058 31 ED xor ebp, ebp 408 | _text:009DBF5D 058 64 A1 18 00 00 00 mov eax, large fs:18h 409 | _text:009DBF63 058 8B 48 08 mov ecx, [eax+8] 410 | _text:009DBF66 058 8B 40 04 mov eax, [eax+4] 411 | _text:009DBF69 412 | _text:009DBF69 loc_9DBF69: ; CODE XREF: CWvsContext::OnEnterField(void)+B2↓j 413 | _text:009DBF69 058 39 C8 cmp eax, ecx 414 | _text:009DBF6B 058 76 07 jbe short loc_9DBF74 415 | _text:009DBF6D 058 83 E8 04 sub eax, 4 416 | _text:009DBF70 058 89 18 mov [eax], ebx 417 | _text:009DBF72 058 EB F5 jmp short loc_9DBF69 418 | ``` 419 | 420 | ## Other Functions 421 | 422 | #### DR_check 423 | This function checks for the debug register. PatchRetZero 424 | 425 | #### HideDll 426 | 427 | This function removes the module from the module list. This crashes on anything higher than Win7. PatchRetZero 428 | 429 | ```cpp 430 | void __cdecl HideDll(HINSTANCE__ *hModule) 431 | { 432 | _LDR_MODULE *pLdrModule; // [esp+0h] [ebp-8h] 433 | 434 | for ( pLdrModule = (_LDR_MODULE *)NtCurrentTeb()->ProcessEnvironmentBlock->Ldr->InLoadOrderModuleList.Flink; 435 | pLdrModule->BaseAddress && pLdrModule->BaseAddress != hModule; 436 | pLdrModule = (_LDR_MODULE *)pLdrModule->InLoadOrderModuleList.Flink ) 437 | { 438 | ; 439 | } 440 | if ( pLdrModule->BaseAddress ) 441 | { 442 | pLdrModule->InLoadOrderModuleList.Blink->Flink = pLdrModule->InLoadOrderModuleList.Flink; 443 | pLdrModule->InLoadOrderModuleList.Flink->Blink = pLdrModule->InLoadOrderModuleList.Blink; 444 | pLdrModule->InMemoryOrderModuleList.Blink->Flink = pLdrModule->InMemoryOrderModuleList.Flink; 445 | pLdrModule->InMemoryOrderModuleList.Flink->Blink = pLdrModule->InMemoryOrderModuleList.Blink; 446 | pLdrModule->InInitializationOrderModuleList.Blink->Flink = pLdrModule->InInitializationOrderModuleList.Flink; 447 | pLdrModule->InInitializationOrderModuleList.Flink->Blink = pLdrModule->InInitializationOrderModuleList.Blink; 448 | pLdrModule->HashTableEntry.Blink->Flink = pLdrModule->HashTableEntry.Flink; 449 | pLdrModule->HashTableEntry.Flink->Blink = pLdrModule->HashTableEntry.Blink; 450 | memset(pLdrModule, 0, 0x48u); 451 | } 452 | } 453 | ``` 454 | 455 | #### SendHSLog 456 | 457 | Self explanatory. Called in WinMain. PatchRetZero 458 | 459 | ```cpp 460 | void __cdecl SendHSLog(unsigned int dwErrCode) 461 | { 462 | ZXString *v1; // eax 463 | ZXString result; // [esp+0h] [ebp-314h] 464 | char szPath[260]; // [esp+4h] [ebp-310h] 465 | char szHShieldPath[260]; // [esp+108h] [ebp-20Ch] 466 | char szCharacterName[260]; // [esp+20Ch] [ebp-108h] 467 | 468 | szPath[0] = 0; 469 | memset(&szPath[1], 0, 0x103u); 470 | szHShieldPath[0] = 0; 471 | memset(&szHShieldPath[1], 0, 0x103u); 472 | szCharacterName[0] = 0; 473 | memset(&szCharacterName[1], 0, 0x103u); 474 | GetModuleFileNameA(0, szPath, 0x104u); 475 | _mbsrchr((const unsigned __int8 *)szPath, 0x5Cu)[1] = 0; 476 | sprintf(szHShieldPath, "%s\\HShield", szPath); 477 | v1 = CConfig::GetSessionCharacterName((CConfig *)TSingleton::ms_pInstance._m_pStr, &result); 478 | sprintf(szCharacterName, "MapleStory_Global:%s", v1->_m_pStr); 479 | if ( result._m_pStr ) 480 | ZXString::_Release((ZXString::_ZXStringData *)result._m_pStr - 1); 481 | _AhnHS_SendHsLogA(dwErrCode, (int)szCharacterName, (int)szHShieldPath); 482 | } 483 | ``` 484 | 485 | #### CeTracer::Run 486 | 487 | This function sends client crash reports. It makes some reporting window pop up. PatchRetZero 488 | 489 | ```cpp 490 | void __thiscall CeTracer::Run(CeTracer *this) 491 | { 492 | if ( this->ET_ErrorCode ) 493 | Start_eTracer(this->ET_ErrorCode, this->ET_MaxErrorCnt); 494 | } 495 | ``` 496 | 497 | ## To Do 498 | - Explain the order of operations / sequence of events 499 | - ` ZApiLoader ` 500 | - Login IP dynamic initializer `ZInetAddr` 501 | - MSLoop_Remove(); 502 | - CWvsApp::ConnectLogin(thisa, v5); 503 | - InitSafeDll_iphdll(); 504 | --------------------------------------------------------------------------------