[Tutorial] Reversing the Client (EP. 1)

02/28/2016 14:42 Cyrex'#1
[ Hey guys. I wanna start a new series of tutorials for reversing
the S4 League Game Client since it is very easy. ]

To get started we need a few things set-up:


[0. x86 (intel syntax) asm knowledge and the ability to think logically]
1. Disassembler (I prefer IDA Pro 6.9 with Hex-Rays plugin)
2. Struct Analyzing tool (ReClass 2015 or Cheat Engine)
3. Executable Dumper (Scylla)
4. Fundamental IDA Pro plugins (Class Informer and String Associate Plugin)
5. C++ knowledge for better understanding of the compiler



After that let's first get a fresh dump of the client. Start up the game with a bypass and dump it with scylla once you are in the lobby to make sure everything is initialized. In this episode we are only applying static analysis so we can close the client again and drag the dumped file into ida pro; it will start to analyze the binary which can take about 5-10 minutes with an average pc. You will notice that the IAT and EAT is broken. Of course it'd be better if it wasn't but we don't need it in this episode. Since I have no point to start reversing we will just use strings.

I found an interesting format string that could give us alot of information:

PHP Code:
.text:01A2A3B8 0000006E C %[%s Actor %pTeamID: %I64dActorID: %I64uP2PID:{%d,%d,%d}, StateID0x%X0x%xHP: %d(%d), MP: %d(%d
This string will get us to know the following points:
  • Live status (dead or alive)
  • Spectator or Player
  • Class ptr
  • TeamID
  • ActorID
  • P2PID
  • StateID
  • HP
  • MP

The most useful thing in here is the class ptr. If you got the instance you can reverse it at runtime(ingame) and find really much stuff out.

Let's now reverse some code by going to the cross-reference of the string.
PHP Code:
.text:00AF7B77                 mov     eax, [ebp+var_10]
.
text:00AF7B7A                 push    eax
.text:00AF7B7B                 mov     ecx, [ebp+var_64]
.
text:00AF7B7E                 push    ecx
.text:00AF7B7F                 mov     edx, [ebp+var_60]
.
text:00AF7B82                 push    edx
.text:00AF7B83                 push    offset aSSActorPTeamid "%s [%s Actor %p] TeamID: %I64d, ActorID"...
.
text:00AF7B88                 lea     eax, [ebp+var_A0]
.
text:00AF7B8E                 push    eax
.text:00AF7B8F                 call    sub_168B4C0
.text:00AF7B94                 add     esp48h 
We can immediately identify the calling convention by looking after the call instruction. The stack is cleaned by the caller. Thus it's __cdecl.
(https://en.wikipedia.org/wiki/X86_calling_conventions)


We can also count the arguments that were passed to this function by dividing 48h with 4 and then convert it to decimal. [ATTENTION: for optimization some compilers only use one add instruction and you cant really see if its only cleaning up one call or few)

Now let's continue with finding out what actually is passed to our function.
The first argument is eax which is &var_A0. Sooo find out what var_A0 is:
Scroll up and lookup an instruction that involves var_A0 as operand.
Nothing is being moved(copied) in var_A0 so it's just used as a reference or pointer to fill some value in it(buffer). Second argument is the format string...
Third argument seems to be a string which is passed in edx, whereas edx is var_60. Let's dig deeper.


PHP Code:
.text:00AF7A40 loc_AF7A40:                             ; CODE XREFsub_AF78A0+197j
.text:00AF7A40                 mov     edx, [ebp+var_10]
.
text:00AF7A43                 mov     eax, [edx]
.
text:00AF7A45                 mov     ecx, [ebp+var_10]
.
text:00AF7A48                 mov     edx, [eax+0D4h]
.
text:00AF7A4E                 call    edx
.text:00AF7A50                 movzx   eaxal
.text:00AF7A53                 test    eaxeax
.text:00AF7A55                 jz      short loc_AF7A60
.text:00AF7A57                 mov     [ebp+var_60], offset aLive "Live"
.text:00AF7A5E                 jmp     short loc_AF7A67
.text:00AF7A60 ; ---------------------------------------------------------------------------
.
text:00AF7A60
.text:00AF7A60 loc_AF7A60:                             ; CODE XREFsub_AF78A0+1B5j
.text:00AF7A60                 mov     [ebp+var_60], offset aDeath "Death" 
here's where var_60 is being set. var_10 is probably some player class or actor class. then the address of the virtual table get's copied into eax and it's calling the 53th virtual function of this class (0D4h / 4 to decimal)[basic index calculating]. This function can be compared to
PHP Code:
virtual bool IsAlive() = 0
. The result of this function gets tested and if it returned true the string which gets copied into var_60 is "Alive" and otherwise "Death".

Wow this took longer to explain than I expected. We will continue this shxt in the next episode. cya.
02/28/2016 15:41 Shinzuya#2
Added to my Overview. :)
Keep it up! Looks nice.
02/28/2016 15:57 Cyrex'#3
PS: I have noticed that the function we were in is also a virtual function and additionally the main loop (it loops thru CTeam* vector and thru CActor* vector). Here's a nice static ptr to CTeamManager: (DWORD)GetModuleBase("S4Client.exe") + 0x2098574

And this is how it loops thru the teams:

PHP Code:
CTeam__thiscall GetTeam(CTeamManagerpThissize_t idx)
{
  
int result// eax@4
  
int v3// [sp+10h] [bp-4h]@2

  
if ( (*(_DWORD *)(*(_DWORD *)(this 4) + 4) - **(_DWORD **)(this 4)) >> <= a2 )
  {
    
result 0;
  }
  else
  {
    
v3 = *(_DWORD *)(this 4);
    if ( (*(
_DWORD *)(v3 4) - *(_DWORD *)v3) >> <= a2 )
      
v57AD3A0C("invalid vector<T> subscript");
    
result = *(_DWORD *)(*(_DWORD *)v3 a2);
  }
  return 
result;

Therefore CTeamManager + 0x4 is CTeams vector. (CTeamManager + 0x0 is vftable).
And this is how it loops thru actors/players:


PHP Code:
CActor__thiscall GetPlayer(CTeampThissize_t idx)
{
  
int result// eax@4
  
int v3// [sp+10h] [bp-4h]@2

  
if ( (*(_DWORD *)(*(_DWORD *)(this 0x24) + 4) - **(_DWORD **)(this 0x24)) >> <= a2 )
  {
    
result 0;
  }
  else
  {
    
v3 = *(_DWORD *)(this 0x24);
    if ( (*(
_DWORD *)(v3 4) - *(_DWORD *)v3) >> <= a2 )
      
v57AD3A0C("invalid vector<T> subscript");
    
result = *(_DWORD *)(*(_DWORD *)v3 a2);
  }
  return 
result;

Therefore CTeam + 0x24 is the players vector.

With this information you can either call the engine functions to get ptrs to specific players, teams
or you can rebuild this function in cpp what i've done for you:


PHP Code:
class CTeamManager;
class 
CTeam;
class 
CActor;

class 
CTeamManager
{
public:
    
voidpVTable//0x0000
    
std::vector<CTeam*>* ppTeams//0x0004
    
    
static CTeamGetTeamById(size_t idx)
    {
        return *(
CTeam**)(*ppTeams idx 4);
    }
}

class 
CTeam
{
public:
    
voidpVTable//0x0000
    
char pad[0x20]; //0x0004
    
std::vector<CActor*>* ppPlayers//0x0024
    
    
static CActorGetPlayerById(size_t idx)
    {
        return *(
CActor**)(*ppPlayers idx 4);
    }


Have fun guys playing with this stuff :P



EDIT:
new stuff:

PHP Code:
.text:00AF7B6F                 mov     ecxdword ptr [ebp+var_78+4]
.
text:00AF7B72                 push    ecx // ActorID
.text:00AF7B73                 mov     edxdword ptr [ebp+var_78]
.
text:00AF7B76                 push    edx //TeamID 
PHP Code:
.text:00AF79F6                 mov     ecx, [ebp+var_48]
.
text:00AF79F9                 add     ecx8
.text:00AF79FC                 call    sub_AFA390
.text:00AF7A01                 movzx   eaxal
.text:00AF7A04                 cdq
.text:00AF7A05                 mov     dword ptr [ebp+var_78], eax
.text:00AF7A08                 mov     dword ptr [ebp+var_78+4], edx 
PHP Code:
TeamID sub_AFA390(var_48+8); 
how it gets calculated in the function itself: (this is var_48+8)
PHP Code:
v3 = *(_BYTE *)(this 2) ^ *(_BYTE *)this
[means values are encrypted :^)]

EDIT2:

that's how you decrypt the teamID:
[Only registered and activated users can see links. Click Here To Register...]
the first byte at selection is *(BYTE*)this which is 'E1' and +2 bytes is 'E1' too. XOR'ing 'E1' with 'E1' results to 0. So the team I'm in has ID 0. (for the ones who does not know, " ^ " <- this character means bitwise exclusive or aka. XOR which is mostly used in games to encrypt things like strings or other values)
03/01/2016 15:56 Cyrex'#4
more info:

how to get game rules (again s4 provides us a static ptr..)
PHP Code:
.text:00AF8761                 mov     ecxds:dword_20968B4
.text:00AF8767                 mov     [ebp+var_114], ecx
.text:00AF876D                 mov     edx, [ebp+var_114]
.
text:00AF8773                 mov     eax, [edx+12Ch]
.
text:00AF8779                 mov     [ebp+var_16C], eax
.text:00AF877F                 mov     ecx, [ebp+var_16C]
.
text:00AF8785                 add     ecx1Ch
.text:00AF8788                 call    sub_AFA400
.text:00AF878D                 mov     [ebp+var_11C], eax
.text:00AF8793                 mov     ecx, [ebp+var_11C]
.
text:00AF8799                 push    ecx
.text:00AF879A                 push    offset aLimitscoreD "LimitScore = %d"
.text:00AF879F                 lea     edx, [ebp+var_244]
.
text:00AF87A5                 push    edx
.text:00AF87A6                 call    sub_168B4C0 
c++
PHP Code:
typedef int (__thiscall *tGetScoreLimit)(voidpThis);
tGetScoreLimit GetScoreLimit 0xAFA400 + (DWORD)GetModuleBase("S4Client.exe");

uintptr_t pECX = *(uintptr_t*)(0x20968B4);
uintptr_t pEAX = *(uintptr_t*)(pECX 0x12C);
pEAX += 0x1C;

^
minimizes to:

voidpObject = *(uintptr_t*)(*(uintptr_t*)(0x20968B4) + 0x12C) + 0x1C;
int LimitScore GetScoreLimit(pObject); 
you can find other game rules around there :P
(more will come, when im back home)
03/01/2016 17:44 onahoe#5
well, finally something useful in this section. :p not bad. keep it up!
03/01/2016 19:33 Cyrex'#6
PHP Code:
ecx = *(DWORD*)(*(DWORD*)(*(DWORD*)(0x28C68CC) + 0x12C) + 0xC);
call    eax [11
Get current round time

virtual call -> 11th index of ecx's(instance ptr) virtual table(vftable)
after the call the result or the current round time gets set in eax
which means the return value of *(DWORD**)(ecx)[11]() is the current round time.

for calling virtuals you just need the correct calling convention which is always __thiscall
parameters get passed normally - pushed onto the stack in x86 before ecx is getting set.
PHP Code:
typedef int (__thiscalltGetRoundTime)(void); 
then just call it like that:
PHP Code:
tGetRoundTime ( * ( uintptr_t** )( ecx )[ 11 ] ) ) ( ) 
hf.
03/02/2016 14:57 H ~#7
Really nice. Keep that shit going ^^
03/03/2016 00:54 ▒ Ant.#8
Wow! Really nice to see someone actually reversing the game for once and not just doing small shit. :D

P.S.: I released the source of my [Only registered and activated users can see links. Click Here To Register...], and it contains some S4 information like this; maybe it will be of assistance.~
03/03/2016 02:14 onahoe#9
Quote:
Originally Posted by ▒ Ant. View Post
Wow! Really nice to see someone actually reversing the game for once and not just doing small shit. :D

P.S.: I released the source of my [Only registered and activated users can see links. Click Here To Register...], and it contains some S4 information like this; maybe it will be of assistance.~
lel just noticed your release now. it's very nice! thanks for that! :p
03/03/2016 15:28 xDarkMan#10
as a member(me ) who is intersted in reversing games. happy to see someone reversing S4 and and posting tips/tuts. helped me alot.
was looking for smthin like that. keep it up.
03/04/2016 00:05 JulietLover#11
Quote:
Originally Posted by Shinzuya View Post
Added to my Overview. :)
Keep it up! Looks nice.
Since you understood !!
So what exactly is this :! so confuse ?
03/04/2016 17:37 SquishHD#12
and this is what? :D