3

Trying to understand a function thats responsible for sending out a packet. I don't understand could it be a integer array or something? or some inline function that's not properly getting rendered in Hex-Rays

I understand the else statement sends a 4 byte packet which contains a timestamp of the GetTickCount API.

The if statement should send the packet that comes in the a2 is the pointer to characters with a3 being the size for all the characters.

Usage is similar to this

char buffer[448];
memset(buffer, 0, sizeof(buffer));
//blah blah packet stuff here
strncpy(&buffer[90], "blah blah blah", 250u);
buffer[339] = 0;
//then the call below.
// 91+250+91 = 432, yet memset is 448, 16 extra probably stack padding.
test(*v28, buffer, strlen(&buffer[90]) + 91);

Here is the original code decompiled from Hex-Rays.

void __thiscall test(void *this, const void *a2, unsigned int a3)
{
  void *v3; // ebx@1
  char *v4; // eax@3
  int v5; // [sp-8h] [bp-418h]@3
  int v6; // [sp-4h] [bp-414h]@3
  char v7[4]; // [sp+Ch] [bp-404h]@4
  char buf[1024]; // [sp+10h] [bp-400h]@3

  v3 = this;
  if ( a2 && (signed int)a3 > 0 )
  {
    *(_DWORD *)buf = 0;
    memcpy(&buf[4], a2, 4 * (a3 >> 2));
    v6 = 0;
    v5 = a3 + 4;
    v4 = buf;
    memcpy(&buf[4 * (a3 >> 2) + 4], (char *)a2 + 4 * (a3 >> 2), a3 & 3);// Looks like Copy by DWORDs, not by Bytes.
  }
  else
  {
    v6 = 0;
    *(_DWORD *)v7 = GetTickCount() / 0xA;
    v5 = 4;
    v4 = v7;
  }
  send(*(_DWORD *)v3, v4, v5, v6);
}

Here I fixed it up a little by hand, still don't understand it.

void __thiscall test(void *this, const void *a2, unsigned int a3)
{
  void *v3; // ebx@1
  char *v4; // eax@3
  int v5; // [sp-8h] [bp-418h]@3
  int v6; // [sp-4h] [bp-414h]@3
  char v7[4]; // [sp+Ch] [bp-404h]@4
  char buf[1024]; // [sp+10h] [bp-400h]@3

  v3 = this;
  if ( a2 && (signed int)a3 > 0 )
  {
    *(_DWORD *)buf = 0;
    //Might be a swap of the 5th offset DWORD to end of the packet?
    //Or maybe it fills in the packet offsetted by the first 4 bytes?
    memcpy(&buf[4], a2, 4 * (a3 / 4)); // Looks like Copy by DWORDs, not by Bytes.
    v6 = 0;
    v5 = a3 + 4;
    v4 = buf;
    //Might be a swap of the end of the packet to the 5th offset DWORD?
    //Looks like some kind of footer to above memcpy function like to finish what the first function couldn't do?
    memcpy(&buf[4 * (a3 / 4) + 4], (char *)a2 + 4 * (a3 / 4), a3 & 3);// Looks like Copy by DWORDs, not by Bytes.
  }
  else
  {
    v6 = 0;
    *(_DWORD *)v7 = GetTickCount() / 0xA;
    v5 = 4;
    v4 = v7;
  }
  send(*(_DWORD *)v3, v4, v5, v6);
}

Okay I gave it some more time could this be the correct?

void __thiscall test(void *this, const void *a2, unsigned int a3)
{
  void *v3; // ebx@1
  char *v4; // eax@3
  int v5; // [sp-8h] [bp-418h]@3
  int v6; // [sp-4h] [bp-414h]@3
  char v7[4]; // [sp+Ch] [bp-404h]@4
  char buf[1024]; // [sp+10h] [bp-400h]@3

  v3 = this;
  if ( a2 && (signed int)a3 > 0 )
  {
    *(_DWORD *)buf = 0;
    memmove(&buf[4],a2,a3 - 4); 

    v6 = 0;
    v5 = a3 + 4;
    v4 = buf;
  }
  else
  {
    v6 = 0;
    *(_DWORD *)v7 = GetTickCount() / 0xA;
    v5 = 4;
    v4 = v7;
  }
  send(*(_DWORD *)v3, v4, v5, v6);
}

Assembly below

.text:00408750 ; =============== S U B R O U T I N E =======================================
.text:00408750
.text:00408750
.text:00408750 ; void __thiscall test(void *this, const void *a2, unsigned int a3)
.text:00408750 test proc near
.text:00408750                                         ; CODE XREF: ServerMainLoop+5DDp
.text:00408750                                         ; ServerMainLoop+64Dp
.text:00408750
.text:00408750 var_404         = byte ptr -404h
.text:00408750 buf             = byte ptr -400h
.text:00408750 a2              = dword ptr  4
.text:00408750 a3              = dword ptr  8
.text:00408750
.text:00408750                 sub     esp, 404h
.text:00408756                 push    ebx
.text:00408757                 push    esi
.text:00408758                 mov     esi, [esp+40Ch+a2]
.text:0040875F                 push    edi
.text:00408760                 test    esi, esi
.text:00408762                 mov     ebx, ecx
.text:00408764                 jz      short loc_408799
.text:00408766                 mov     eax, [esp+410h+a3]
.text:0040876D                 test    eax, eax
.text:0040876F                 jle     short loc_408799
.text:00408771                 mov     ecx, eax
.text:00408773                 lea     edi, [esp+410h+buf+4]
.text:00408777                 mov     edx, ecx
.text:00408779                 mov     dword ptr [esp+410h+buf], 0
.text:00408781                 shr     ecx, 2
.text:00408784                 rep movsd
.text:00408786                 mov     ecx, edx
.text:00408788                 push    0
.text:0040878A                 and     ecx, 3
.text:0040878D                 add     eax, 4
.text:00408790                 push    eax
.text:00408791                 lea     eax, [esp+418h+buf]
.text:00408795                 rep movsb
.text:00408797                 jmp     short loc_4087B7
.text:00408799 ; ---------------------------------------------------------------------------
.text:00408799
.text:00408799 loc_408799:                             ; CODE XREF: test+14j
.text:00408799                                         ; test+1Fj
.text:00408799                 call    ds:GetTickCount
.text:0040879F                 mov     edx, eax
.text:004087A1                 mov     eax, 0CCCCCCCDh
.text:004087A6                 mul     edx
.text:004087A8                 shr     edx, 3
.text:004087AB                 push    0               ; flags
.text:004087AD                 mov     dword ptr [esp+414h+var_404], edx
.text:004087B1                 push    4               ; len
.text:004087B3                 lea     eax, [esp+418h+var_404]
.text:004087B7
.text:004087B7 loc_4087B7:                             ; CODE XREF: test+47j
.text:004087B7                 mov     ecx, [ebx]
.text:004087B9                 push    eax             ; buf
.text:004087BA                 push    ecx             ; s
.text:004087BB                 call    send
.text:004087C0                 pop     edi
.text:004087C1                 pop     esi
.text:004087C2                 pop     ebx
.text:004087C3                 add     esp, 404h
.text:004087C9                 retn    8
.text:004087C9 test endp
.text:004087C9
.text:004087C9 ; ---------------------------------------------------------------------------
5
  • Splitting a single buffer copy into two steps, one by DWORDs and one by BYTEs, is a common optimization technique. Hex-Rays is not infallible, it might be tricked by some instructions mixed into the middle of the loop. Since you're not providing the disassembled code, it's hard to tell for sure, but memcpy is more likely than memset.
    – DCoder
    Apr 28, 2014 at 12:34
  • Assembly posted in topic too now. I had similar issues where memcpy turned out to be a memset undercover the >> 2 always revealed it, so I think it could be a memset variant. rep movsd and rep movsb must be them. Apr 28, 2014 at 12:41
  • With the disassembly, this is easy - rep movs* copies data from one buffer to another, like memcpy. rep stos* writes constant data to a buffer, like memset does.
    – DCoder
    Apr 28, 2014 at 12:47
  • So could both be just one function? I don't really understand why I need to do 2 operations for something as simple as moving everything 4 bytes to the right. (I think it does that) I have a buffer with 1024 bytes to use and no other variables. Honestly I don't look at assembly unless I have a big mathematical monster too hard for me and other times I just get lucky. Apr 28, 2014 at 12:53
  • It's probably memmove then memset. Apr 28, 2014 at 13:02

2 Answers 2

5

This code is easy, it's just a bit messy 'cause it was generated by a decompiler. Here's a much simpler commented version :

void __thiscall test(void *this, const void *a2, unsigned int a3)
 {
    char v7[4]; 
    int  v5 = 4;
    void *v3 = this;
    char buf[1024], *v4;

    if (a2 != NULL && (signed int)a3 > 0)
    {
      //Setting the 4 first bytes to 0. Certainly the message header !
      *(_DWORD *)buf = 0;

      /*
         Same as :
         memset(buff, 0, 4);
         buf[0] = buf[1] = buf[2] = buf[3] = 0; 
       */

      /*
         Copying the first a3 bytes of a2 into buff + 4. 
         The + 4 is to jump the 4 bytes header set to 0 previously. 
       */ 
      memcpy(buf + 4, a2, a3); //There's no point in 4 * a3 / 4;

      //Size has changed to a3 + 4 (4 bytes for the header)
      v5 += a3;

      //
      v4 = buf;
    }
    else
    {
       /* _DWORD is 4 bytes. This line converts v7 into an integer to
           copy the value of GetTickCount() / 10 byte by byte into it.  
        */
      *(_DWORD *)v7 = GetTickCount() / 10;

      //
      v4 = v7;
    }

    send(*(_DWORD *)v3, v4, v5, 0);
 }

From this I would say that this routine sends a packet with the header set to GetTickCount() / 10 if there's no message (a2 == NULL or a3 <= 0), otherwise it sets the message header to 0, the message itself to a2 and sends the packet.

The thing about the original version you provided is that it was performing a copy 4 elements by 4 ... That's why the memcpy was split in two. The first one copying the elements 4 * (a3 / 4) (if a3 = 19 then it would've copied 16 elements), the second one copying the remaining a3 % 4 (a3 % 4 <==> a3 & (4 - 1) <==> a3 & 3) elements (if a3 = 19 then it would've copied 3).

10
  • the message header is part of the first 4 bytes in the packet right? So it's used like either it could be the GetTickCount or otherwise it's filled zero's to indicate a message? Apr 28, 2014 at 13:09
  • Thanks for putting the time into refactoring it. Do the first 4 bytes of the packet get lost though? Apr 28, 2014 at 13:15
  • 1
    You're welcome.
    – yaspr
    Apr 28, 2014 at 13:18
  • Oh i figured it out no bytes are lost just padded with 4 empty bytes in front. Apr 28, 2014 at 13:21
  • Exactly :D That's just a header. I'm sure that the routine which is supposed to receive the message checks the header to know whether it should, probably, sync or read the message body.
    – yaspr
    Apr 28, 2014 at 13:23
5

It's not a memset at all, it's just a memcpy. Copying dword by dword is more efficient than byte by byte, since 32 bits of data bus can be used per copy (at least if the data is word aligned). So what the compiler does is:

  • divide the number of bytes by 4, by shifting cx right by 2 bits
  • copy the appropriate number of 4-byte-dwords, using movsd
  • calulate the remainder of the number of bytes (by AND-ing cx with 3)
  • copy these bytes one-by-one, using movsb

What adds to the confusion is that the compiler already prepares the arguments for the next function call while still doing the memcpy - the push 0, add eax, 4 and push eax belong to the send call after the jmp.

3
  • Good information will come in handy, sorry I couldn't accept both answers as best answer. Apr 28, 2014 at 13:20
  • 1
    Well, I do agree on the fact that copying DWORD by DWORD is faster than copying BYTE by BYTE. But, sometimes, if the compiler does its job properly & if the architecture handles it, a vectorized version will be used instead (memcpy_SSE42, ...). That's what usually happens when using GCC (for static & dynamically linked binaries).
    – yaspr
    Apr 28, 2014 at 13:32
  • Agreed, but i wanted to explain why the specific code the OP posted looks like it does ;) Apr 28, 2014 at 13:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.