Twisted 发表于 2011-10-17 08:25:39

[1.298] Type3Cancel(), Type4Cancel(), Type6Cancel(), Type9Cancel() exploit fix

It seems that there is another "ril pro hacker" going around convincing themself that vulnerabilities cannot be patched, so here I am to prove them wrong.

First, let's go over how the vulnerability works. For this, we'll need the help of the old source. This thread is actually for three vulnerabilities, though they are exactly the same, so I'll use only CMagicProcess::Type4Cancel() (the one the ril pro hacker was exploiting!).

Assume this ril pro hacker sends a malformed packet while reading through the code. See if you can work out what exactly is bad here -- before I point it out!// Declare our function
void CMagicProcess::MagicPacket(char *pBuf, int len)
{
                // Declare variables & set pointers to NULL
      int index = 0, send_index = 0, magicid = 0, sid = -1, tid = -2, data1 = 0, data2 = 0, data3 = 0, data4 = 0, data5 = 0, data6 = 0;
                int type3_attribute = 0;
      char send_buff;
      _MAGIC_TABLE* pTable = NULL;
      CNpc* pMon = NULL;

                // Use memset() to fill the send_buff char array with 0x00s
                memset( send_buff, NULL, 128 );

                // Read data from the packet!
      BYTE command = GetByte( pBuf, index );// Sub opcode
      magicid = GetDWORD( pBuf, index );                // Skill ID
      sid = GetShort( pBuf, index );                  // Session/socket ID of source user
      tid = GetShort( pBuf, index );                                 // Session/socket ID of target user
      data1 = GetShort( pBuf, index );                  // Additional info (not relevant)
      data2 = GetShort( pBuf, index );               
      data3 = GetShort( pBuf, index );
      data4 = GetShort( pBuf, index );
      data5 = GetShort( pBuf, index );
      data6 = GetShort( pBuf, index );

                // === IGNORE THIS: NOT RELEVANT ===
      if( m_pSrcUser )      {
                if( m_pSrcUser->m_pUserData->m_bZone == ZONE_SNOW_BATTLE && m_pMain->m_byBattleOpen == SNOW_BATTLE )    {
                        if( magicid != SNOW_EVENT_SKILL )               return;
                }
      }

                // If the packet's sub opcode (command) is MAGIC_CANCEL (0x06), we'll execute our cancel funcs (wherein the crash occurs).
      if (command == MAGIC_CANCEL) {
                Type3Cancel(magicid, sid);       // Type 3 cancel procedure. You can see it passes in the skill's ID and the ID of the source user.
                Type4Cancel(magicid, sid);// Type 4 cancel procedure. Likewise. ^
                return;
      }Following on from that, we're lead to Type3Cancel() and then Type4Cancel(). As I said, for all intents and purposes the functions are identical, so we'll only look at Type4Cancel():// Declare function
void CMagicProcess::Type4Cancel(int magicid, short tid)
{
                // Declare and initialise variables & pointers
      int send_index = 0;      
      char send_buff;
      memset( send_buff, NULL, 128);
      CUser* pTUser = NULL;                                                                  
      _MAGIC_TYPE4* pType = NULL;
      BOOL buff = FALSE;

                // Find the pointer to the target ID passed from the client
      pTUser = (CUser*)m_pMain->m_Iocport.m_SockArray;

                // If the returned pointer(pTUser) from the socket array (m_SockArray) is 0/NULL/false, stop here!
      if( !pTUser ) return;

                // See if the skill is a type 4 skill (if it exists in the type 4 skill array, it's a type 4 skill -- DUH!)
      pType = m_pMain->m_Magictype4Array.GetData( magicid );

                // If the returned pointer(pType) from the type 4 skill array (m_Magictype4Array) is 0/NULL/false, stop here!
      if ( !pType ) return;

                // As it's not NULL, we'll use pType to find the buff type.
      BYTE buff_type = pType->bBuffType;

                // We'll then use the buff type to determine what we'll do next!
      switch (buff_type) {            

                              // If the buff type is 1, we'll proceed to do our stuff.. with our pointer to the user.
                case 1:
                        if (pTUser->m_sMaxHPAmount > 0) {
                              pTUser->m_sDuration1 = 0;               
                              pTUser->m_fStartTime1 = 0.0f;
                              pTUser->m_sMaxHPAmount = 0;            
                              buff = TRUE;
                        }
                        break;Didn't pick up on it?

Hint, it crashes any time it accesses the user pointer, for example, here:       if (pTUser->m_sMaxHPAmount > 0) {(Which is where the ril pro hacker was causing it to crash)

Still don't see it?

The problem is with:
      CUser* pTUser = NULL;                                          
      pTUser = (CUser*)m_pMain->m_Iocport.m_SockArray;m_SockArray is an array. Arrays are pre-initialised before use; they have a fixed size! That means, anything outside of the preallocated boundaries is going to result in it not being able to access the right data.

Let's find where the array is initialised:
void CIOCPort::Init(int serversocksize, int clientsocksize, int workernum)
{
      m_SocketArraySize = serversocksize;
      m_ClientSockSize = clientsocksize;
      
      m_SockArray = new CIOCPSocket2* ;So, the socket array has a range of "serversocksize". Okay, but what's that? For this, we'll need to find where this function's called - as the variable is passed into the function as an argument.
      m_Iocport.Init( MAX_USER, CLIENT_SOCKSIZE, 4 );...so, the first argument (int serversocksize) is MAX_USER -- which is:#define MAX_USER                        1500WHICH MEANS, that the socket array is allocated to hold up to 1500 (0-1499) sockets (pointers to instances of CIOCPSocket2); it can hold no more than that.      CUser* pTUser = NULL;                                          
      pTUser = (CUser*)m_pMain->m_Iocport.m_SockArray;The problem arises when a socket ID is passed into the array that is not within the 0-1499 range. There is, of course, a check following it to see if it returns NULL/0:                // If the returned pointer(pTUser) from the socket array (m_SockArray) is 0/NULL/false, stop here!
      if( !pTUser ) return;... but it won't return NULL/0. Here's the problem code from the 1.298 Ebenezer:MOV ESI,DWORD PTR DS: // grab the pointer from the socket array (store it in ESI/pTUser)
CMP ESI,EBX // if ( !pTUser )... (with the following conditional jump after of course, to determine the actual "if condition" but you get the idea)You can see that it's actually calculating the address (ECX + ), there's no boundaries here. This code knows nothing about the boundaries of the array, it's just calculating the address and returning whatever's there -- so, depending on the socket ID used, we could be returning just about *ANYTHING* in memory. This means that our NULL check is unlikely to prevent anything bad from happening.

So, what we have to do is tell it our boundaries. We have to check to make sure that the socket ID is actually 0-1499 - and that's what I do!

Type4Cancel:
Pick a spot before the array and after we get easy access to the socket ID and jump to our code-cave!
0046D435      ^EB 84            JMP SHORT 0046D3BB
0046D437         90                      NOPMeanwhile, in the code-cave:
Note: EAX is the socket ID.

Compare EAX to 5DC (1500)0046D3BB         3D DC050000    CMP EAX,5DCIf the socket ID is >= (greater than or equal to) 1500, jump to the end of Type4Cancel() (so nothing gets a chance to crash!)0046D3C0         0F8D B4040000JGE 0046D87ANow we'll check to see if it's below 0 -
Compare the socket ID to 0.0046D3C6         83F8 00                CMP EAX,0If the socket ID is lower than 0, jump to the end of Type4Cancel() (so nothing gets a chance to crash!)0046D3C9         0F8C AB040000JL 0046D87AWell, it's within the boundaries... so we should be fine to pull the pointer from the socket array (the code I patched over -- we have to obviously have this code execute sometime, otherwise the function would be pointless -- so we stick it in our code-cave):0046D3CF         8B3481          MOV ESI,DWORD PTR DS:..and finally jump back to finish executing Type4Cancel():0046D3D2         EB 64            JMP SHORT 0046D438The exact same thing for Type3Cancel():
0046D9CA         3D DC050000    CMP EAX,5DC
0046D9CF         0F8D 0F010000JGE 0046DAE4
0046D9D5         83F8 00                CMP EAX,0
0046D9D8         0F8C 06010000JL 0046DAE4
0046D9DE         8B3481          MOV ESI,DWORD PTR DS:
0046D9E1         EB 32            JMP SHORT 0046DA15
..
0046DA12      ^EB B6            JMP SHORT 0046D9CA
0046DA14         90                      NOP..and again for Type6Cancel():
0046D277   3D DC050000    CMP EAX,5DC
0046D27C   0F8D 31010000JGE 046D3B3
0046D282   83F8 00      CMP EAX,0
0046D285   0F8C 28010000JL 0046D3B3
0046D28B   8B3481         MOV ESI,DWORD PTR DS:
0046D28E   EB 35          JMP SHORT 0046D2C5
..
0046D2C2    ^EB B3          JMP SHORT 0046D277
0046D2C4   90             NOPAnd the same for Type9Cancel():0046DD47 85C0 TEST EAX,EAX
0046DD49 0F8C 1C020000 JL 0046DF6B
0046DD4F 3D DC050000 CMP EAX,5DC
0046DD54 0F8D 11020000 JGE 0046DF6B
0046DD5A 8B3481 MOV ESI,DWORD PTR DS:
0046DD5D EB 3C JMP SHORT 0046DD9B

0046DD98 ^\EB AD JMP SHORT 0046DD47
0046DD9A 90 NOPHave fun!
页: [1]
查看完整版本: [1.298] Type3Cancel(), Type4Cancel(), Type6Cancel(), Type9Cancel() exploit fix