I decided to finally go ahead and reverse engineer the osu! executable. First, osu has some "defences" against reverse engineering, which are all located in the osu!auth.dll.
However, one thing osu doesn't implement is any CRC or MD5 checking, meaning, although it does check if the dll exists, we can just remove the contents of the dll and keep it there as a placeholder with it having no actual purpose. Then, osu will allow you to attach any debugger you wish. In my case, I'm using the CE debugger.
After doing so, you can attach any debugger of your choice and then we can start finding some useful things. From experience, I know that osu relies on the audio time to calculate its own game time to prevent any latency etc. From this, we know that whenever we start playing, the game's audio time will increase as it progresses. We can then find a number with an initial value of 0 which increases and filter it down ensuring that no track is initially playing.
Now we have the memory address of a few possible values which we need to be able to get dynamically whenever the game opens, rather than having to statically find the memory address each time. To do this we can implement something popularly known as signature/pattern scanning in which you scan for a certain pattern of bytes in the memory of a process to find the location of memory you are looking for. To create a signature in this instance we can find what accesses and/or writes to this value. Here we can see an assembly breakdown of the opcodes that write to our desired memory address which in this case is 05196A2C.
Code:
09EA04A3 - DB 5C 24 34 - fistp dword ptr [esp+34]
09EA04A7 - 8B 44 24 34 - mov eax,[esp+34]
09EA04AB - A3 2C6A1905 - mov [05196A2C],eax <<
09EA04B0 - D909EA 5 -
fldz 24 10 - fstp qword ptr [esp+10]
From this, we can create a pattern using the bytes around our desired address which in this case are DB 5C 24 34 8B 44 24 34 A3 in that order. However, when we pattern scan to this address we will get to the start of this pattern, not our desired memory address. To solve this, we can simply add an offset of, in this case, 9 bytes which will end us at our memory address which points to the game's audio time.
A similar process can be used to find the current game state that the game is in. From the 2016 source leak of osu! we can see that the game's current state is stored in an enum shown
below
enum class OsuModes : std::int32_t
{
Menu = 0,
Edit,
Play,
Exit,
SelectEdit,
SelectPlay,
SelectDrawings,
Rank,
Update,
Busy,
Unknown,
Lobby,
MatchSetup,
SelectMulti,
RankingVs,
OnlineSelection,
OptionsCosetWizard
,
RankingTagCoam,
Ranking ,
PackageUpdater,
Benchmark,
Tourney,
Charts
};
From this enum, we can once again deduce the memory address of the current game state, which I've already done. Once again we check which opcodes write to this memory address which in my case was 051967F8. An example of the assembly breakdown can be shown below.
16D7BE94 - 8B 4D 90 - mov ecx,[ebp-70]
16D7BE97 - FF 15 FC701312 - call dword ptr [121370FC]
16D7BE9D - 83 3D F8671905 02 - cmp dword ptr [051967F8],02 <<
16D7j8 =nBnBrmL0K =::#=zuCAj8eg=+D6
16D7BEA6 - 80 3D 19691905 00 - cmp byte ptr [05196919],00
Now creating a signature, in this case, is a little more complex since we have other memory addresses pointing to unknown values in the way.
To do this we use a placeholder which is most popularly signed by a ? so the signature for this memory address would be 8B 4D 90 FF 15 ? ? ? ? 83 3D and we would need to add an additional offset of 11 bytes to get to our desired memory address.
However, one thing osu doesn't implement is any CRC or MD5 checking, meaning, although it does check if the dll exists, we can just remove the contents of the dll and keep it there as a placeholder with it having no actual purpose. Then, osu will allow you to attach any debugger you wish. In my case, I'm using the CE debugger.
After doing so, you can attach any debugger of your choice and then we can start finding some useful things. From experience, I know that osu relies on the audio time to calculate its own game time to prevent any latency etc. From this, we know that whenever we start playing, the game's audio time will increase as it progresses. We can then find a number with an initial value of 0 which increases and filter it down ensuring that no track is initially playing.
Now we have the memory address of a few possible values which we need to be able to get dynamically whenever the game opens, rather than having to statically find the memory address each time. To do this we can implement something popularly known as signature/pattern scanning in which you scan for a certain pattern of bytes in the memory of a process to find the location of memory you are looking for. To create a signature in this instance we can find what accesses and/or writes to this value. Here we can see an assembly breakdown of the opcodes that write to our desired memory address which in this case is 05196A2C.
Code:
09EA04A3 - DB 5C 24 34 - fistp dword ptr [esp+34]
09EA04A7 - 8B 44 24 34 - mov eax,[esp+34]
09EA04AB - A3 2C6A1905 - mov [05196A2C],eax <<
09EA04B0 - D909EA 5 -
fldz 24 10 - fstp qword ptr [esp+10]
From this, we can create a pattern using the bytes around our desired address which in this case are DB 5C 24 34 8B 44 24 34 A3 in that order. However, when we pattern scan to this address we will get to the start of this pattern, not our desired memory address. To solve this, we can simply add an offset of, in this case, 9 bytes which will end us at our memory address which points to the game's audio time.
A similar process can be used to find the current game state that the game is in. From the 2016 source leak of osu! we can see that the game's current state is stored in an enum shown
below
enum class OsuModes : std::int32_t
{
Menu = 0,
Edit,
Play,
Exit,
SelectEdit,
SelectPlay,
SelectDrawings,
Rank,
Update,
Busy,
Unknown,
Lobby,
MatchSetup,
SelectMulti,
RankingVs,
OnlineSelection,
OptionsCosetWizard
,
RankingTagCoam,
Ranking ,
PackageUpdater,
Benchmark,
Tourney,
Charts
};
From this enum, we can once again deduce the memory address of the current game state, which I've already done. Once again we check which opcodes write to this memory address which in my case was 051967F8. An example of the assembly breakdown can be shown below.
16D7BE94 - 8B 4D 90 - mov ecx,[ebp-70]
16D7BE97 - FF 15 FC701312 - call dword ptr [121370FC]
16D7BE9D - 83 3D F8671905 02 - cmp dword ptr [051967F8],02 <<
16D7j8 =nBnBrmL0K =::#=zuCAj8eg=+D6
16D7BEA6 - 80 3D 19691905 00 - cmp byte ptr [05196919],00
Now creating a signature, in this case, is a little more complex since we have other memory addresses pointing to unknown values in the way.
To do this we use a placeholder which is most popularly signed by a ? so the signature for this memory address would be 8B 4D 90 FF 15 ? ? ? ? 83 3D and we would need to add an additional offset of 11 bytes to get to our desired memory address.
Moderatörün son düzenlenenleri: