sidneys1.com
Open in
urlscan Pro
2607:ff18:80::5ba4
Public Scan
Submitted URL: https://t.co/2p4FaLYqrT
Effective URL: https://sidneys1.com/reverse-engineering/2023/03/16/reverse-engineering-a-win95-game-II.html
Submission: On March 19 via manual from US — Scanned from DE
Effective URL: https://sidneys1.com/reverse-engineering/2023/03/16/reverse-engineering-a-win95-game-II.html
Submission: On March 19 via manual from US — Scanned from DE
Form analysis
0 forms found in the DOMText Content
Sidneys1.com TagsCategoriesSeries REVERSE ENGINEERING A WINDOWS 95 GAME REVERSING (UNDOCUMENTED) SETTINGS Mar 16, 2023 • Sidneys1 • programming • reverse-engineering • ghidra This post has multiple parts: * Reverse Engineering a Windows 95 Game – Reversing Asset Storage (Part I) * Reverse Engineering a Windows 95 Game – Reversing (Undocumented) Settings (Part II) I recently rediscovered an obscure 1997 Simon & Schuster / Marshall Media edutainment game for Windows 95 that I played as a kid: Math Invaders. In this part, we’ll investigate disassembling and reverse engineering the binary to identify an undocumented settings file format. -------------------------------------------------------------------------------- As our reverse engineering tool of choice, we’ll be using the National Security Agency’s Ghidra. This powerful tool allows us to disassemble the MATHINV.EX_ binary that is bundled on the disk. The first bit of information we get when ingesting the binary in Ghidra is an “Import Results Summary” dialog, with information about the binary itself. Here’s some excerpts: Compiler: visualstudio:unknown Debug Misc: Release/sspyth.exe PDB File: sspyth.pdb PE Property[FileDescription]: SSPYTH MFC Application Interesting - the project in visual studio seems to have originally been called “sspyth”, short for “S.S. Pythagoras”, the name of the protagonist’s ship within the game. Let’s try and identify the entrypoint. This is a Windows MFC program, which means the actual entrypoint is “runtime code” that will identify the main MFC module within the program and initialize it. So instead of looking for this enrtypoint (which Ghidra finds for us and names entry), we will try and find the main MFC module initializer by searching for something we know happens early in the program’s execution. When first run, the game checks that DirectX, DirectPlay, and the game CD are inserted. Using Ghidra’s Search → For Strings... tool we’ll find the “Please insert CD” message. Clicking the result will select the data in the CodeBrowser, and right-clicking the automatically created symbol allows us to click References → Find references to s_Please_insert... to find all references to this particular value within the codebase. Doing so brings up one result at 0x0042cb86. Clicking the result takes us to the relevant address. The disassembly shows us a function called FUN_0042ca2f(CWinApp *param_1), which we’ll renamed to CWinAppEntrypoint. As this function is not called anywhere else in the code, we can be fairly confident that this is only called by runtime code that gets its address programmatically. Disassembly of CWinAppEntrypoint (click to expand). void CWinAppEntrypoint(CWinApp *param_1) { int iVar1; undefined4 *puVar2; FILE *_File; undefined4 local_28c; BYTE local_21c [264]; char local_114 [260]; void *pvStack_10; undefined *puStack_c; undefined4 local_8; local_8 = 0xffffffff; puStack_c = &LAB_0042cc3d; pvStack_10 = ExceptionList; ExceptionList = &pvStack_10; CWinApp::Enable3dControlsStatic(param_1); CWinApp::LoadStdProfileSettings(param_1,4); FID_conflict:__mbscpy((char *)local_21c,&DAT_00495378); FUN_0042d603(s_Version_0049537c,local_21c); iVar1 = _strcmp(s_1.00-Rel_00495384,(char *)local_21c); if (iVar1 != 0) { AfxMessageBox(s_Game_not_installed,_run_the_setu_00495390,0x10,0); FUN_0042cc47(); return; } FID_conflict:__mbscpy((char *)local_21c,&DAT_004953bc); FID_conflict:__mbscpy(local_114,&DAT_004953c0); GetPrivateProfileStringA (s_MazePath_004953dc,s_pakpath_004953d4,&DAT_004953d0,local_114,0x104, s_.\3d.ini_004953c4); puVar2 = (undefined4 *)_strlen(local_114); if (puVar2 == (undefined4 *)0x0) { FUN_0042d603(s_pakpath_004953e8,local_21c); FID_conflict:_strcat((char *)local_21c,s_game.pak_004953f0); while (_File = FID_conflict:__wfopen((char *)local_21c,&DAT_004953fc), _File == (FILE *)0x0) { iVar1 = AfxMessageBox(s_Please_insert_the_Math_Invaders_C_00495400,0x11,0); if (iVar1 == 2) { FUN_0042cc47(); return; } } puVar2 = (undefined4 *)_fclose(_File); } AfxSetAllocStop(0x53b0); local_8 = 0; if (puVar2 == (undefined4 *)0x0) { local_28c = 0; } else { local_28c = FUN_0042e186(puVar2); } local_8 = 0xffffffff; *(undefined4 *)(param_1 + 0x1c) = local_28c; FUN_0042e2e0(*(int **)(param_1 + 0x1c)); FUN_0042cc47(); return; } Alright! We can already see some useful things here. FUN_0042d603 gets a value from the game’s Registry key, so that line just checks that the program is installed. In fact, we can just rename FUN_0042d603 to GetValueFromRegistry. Further down we see a GetPrivateProfileStringA call. I had to look this function up as it’s somewhat esoteric, but it and the whole GetPrivateProfile* still supported in today’s Win32 API! > Retrieves a string from the specified section in an initialization file. > GetPrivateProfileStringA function (winbase.h) - Win32 apps | Microsoft Learn This description undersells this singular function call - when called the GetPrivateProfileXxx family of APIs will open and read a given *.ini file, parse it, and return the value in the specified [section] and key=. If the given file does not exist, it will return the default value. And, using Ghidra’s Symbol Tree, we can find all calls to the GetPrivateProfileXxx APIs and the parameters used. Doing so provides us with this list of parameters, expected to be found in .\3d.ini (relative to the CWD). These are mostly loaded in another function called by CWinAppEntrypoint: FUN_0042e2e0, which we can rename to LoadSettings: [MazePath] pakpath = ; String datapath = ; String diskpath = ; String lastfile = ; String room = ; String usepakfile = 0 ; Integer. In practice it is used as a boolean, ; where 0 is FALSE, and anything else is TRUE. [Render] fullscreen = 1 ; Integer winsize = 10 ; Integer textdetail = 10 ; Integer Let’s see if this works. Let’s just create a C:\MathInvaders\3d.ini and as a simple test, we’ll set [Render]→fullscreen to 0, and… Well… Sort of. Ok, the game doesn’t actually run, and there’s a weird white space at the bottom of the window. But we’ve proven it works! But what’s intriguing to me is the [MazePath] section of the config… I wonder what we could use those settings for. In particular, the fullscreen setting is loaded into a global variable that we’ll call gFullscreen - this factors into to code processing some very interesting strings about an “editor mode”… I wonder if we can activate that? if (gFullscreen == 0) { if (*(int *)(param_1 + 0x334) == 0) { _sprintf(local_104,s__Math_Invaders_-_NO_ACTIVE_LEVEL_0049585c); } else { __splitpath(&DAT_0049c7c8,local_1fc,local_1f4,local_12c,local_10c); _sprintf(local_104,s__Math_Invaders_-_'%s'_004957f4,local_12c); if (*(int *)(param_1 + 0x3714) == 0) { FID_conflict:_strcat(local_104,s__-_***_EDITOR_MODE_***_00495810); } iVar1 = CSplitterWnd::IsTracking((CSplitterWnd *)(param_1 + 0x2e0)); if (iVar1 == 0) { FID_conflict:_strcat(local_104,s__-_Running..._0049584c); } else { FID_conflict:_strcat(local_104,s__-_Paused,_press_'p'_to_resume._00495828); } } CWnd::SetWindowTextA(param_1,local_104); } Next time! -------------------------------------------------------------------------------- This post has multiple parts: * Reverse Engineering a Windows 95 Game – Reversing Asset Storage (Part I) * Reverse Engineering a Windows 95 Game – Reversing (Undocumented) Settings (Part II) -------------------------------------------------------------------------------- Like what you read? Consider buying me a coffee. Disqus Comments Please enable JavaScript to view the comments powered by Disqus. SIDNEYS1.COM * Contact * admin@sidneys1.com * sidneys1@proton.me * Matrix Chat * SimpleX Chat * Alternative Access * Sidneys1.com on Tor * Sidneys1.com on IPFS * Sidneys1.com on GitHub Pages * Sidneys1 * Sidneys1 * Sidneys1 A home for all my ramblings on subjects such as programming, cybersecurity, photography, videography, video games, and whatever else I see fit. Tor Snowflake > Snowflake is a system that allows people from all over the world to access > censored websites and applications. Similar to how VPNs assist users in > getting around Internet censorship, Snowflake helps you avoid being noticed by > Internet censors by making your Internet activity appear as though you're > using the Internet for a regular video or voice call. — The Tor Project