首页
社区
课程
招聘
[原创]QQ断网探测逆向分析
发表于: 2015-12-18 15:48 6739

[原创]QQ断网探测逆向分析

2015-12-18 15:48
6739

QQ中有一个断网探测功能,当网络断开时,QQ在1~2秒钟内就可以知道并且刷新用户状态,当网络畅通时又可以迅速连接服务器。对这个功能,网上有多种猜测实现的方法,有说不停的ping,有说访问dns的,有说是心跳包保持连接的。这次通过逆向的方式一探究竟。

研究的QQ版本:

操作系统平台:win7 32位

Od附加QQ ,选择od菜单->查看->可执行模块,从列表框中选择common.dll模块



在od反汇编窗口选择查找->当前模块中的名称



在util::network::isNetConnectionOK2下断点



 代码比较简单,只有几句话



Call 67e58107是关键call, F7进入67e58107



这段代码od调试时直接执行
mov eax,dword ptr[68017530] ;
test eax,eax,rea;
jnz 跳转
经过阅读主要意思就是判断68017530这个地址的指针指向的内容是否为0,
可以肯定这是读取标示位确定如何返回断网还是联网的。
查看内存地址如下图:



对01位置下内存写入断点,程序立刻断在如下函数位置



经过多次跟踪,发现程序的关键call是 call 67E58D96,继续od F8跟流程,最后会发现程序进入 call 67e58268,这里开始调用winapi函数,拿出IDA直接加载common.dll,F5阅读伪代码。 代码如下
void __cdecl sub_67E58268(int a1, int a2, int a3, LPLONG Target)
{
  struct _MIB_IPFORWARDTABLE *v4; // eax@4
  struct _MIB_IPFORWARDTABLE *v5; // esi@5
  unsigned int v6; // edi@7
  unsigned int v7; // edx@7
  int v8; // esi@8
  unsigned int v9; // ecx@11
  int v10; // ecx@14
  const wchar_t *v11; // edi@21
  const wchar_t *v12; // esi@21
  int v13; // eax@22
  __int32 **v14; // edi@22
  int v15; // edx@22
  int v16; // ecx@22
  int **v17; // eax@22
  int v18; // ecx@22
  int v19; // edx@22
  void *v20; // eax@25
  char *v21; // esi@29
  DWORD v22; // edx@29
  int v23; // ecx@30
  char *v24; // edi@30
  int v25; // eax@38
  int v26; // edx@38
  int v27; // ecx@38
  int **v28; // eax@38
  int v29; // edx@38
  int v30; // ecx@38
  int v31; // edx@38
  int v32; // ecx@38
  int **v33; // eax@38
  int v34; // edx@38
  int v35; // eax@39
  int v36; // [sp-14h] [bp-4Ch]@38
  int *v37; // [sp-10h] [bp-48h]@22
  int v38; // [sp-Ch] [bp-44h]@22
  int *v39; // [sp-8h] [bp-40h]@22
  __int32 *v40; // [sp-4h] [bp-3Ch]@22
  char v41; // [sp+Ch] [bp-2Ch]@38
  char v42; // [sp+10h] [bp-28h]@38
  char v43; // [sp+14h] [bp-24h]@38
  int **v44; // [sp+18h] [bp-20h]@22
  int v45; // [sp+1Ch] [bp-1Ch]@7
  ULONG v46; // [sp+20h] [bp-18h]@3
  unsigned int v47; // [sp+24h] [bp-14h]@7
  ULONG pdwSize; // [sp+28h] [bp-10h]@3
  void *v49; // [sp+2Ch] [bp-Ch]@3
  PMIB_IPADDRTABLE pIpAddrTable; // [sp+30h] [bp-8h]@3
  unsigned int v51; // [sp+34h] [bp-4h]@1
  __int32 **v52; // [sp+40h] [bp+8h]@38

  *(_DWORD *)a1 = 0;
  *(_DWORD *)a2 = 0;
  *(_DWORD *)a3 = -1;
  CTXStringW::Empty(Target);
  v51 = -1;
  if ( !sub_67E5812D((unsigned __int32 *)&v51) || v51 == -1 )
  {
    *(_DWORD *)a1 = 1;
    return;
  }
  pdwSize = 0;
  v46 = 0;
  v49 = 0;
  pIpAddrTable = 0;
  GetIpForwardTable(0, &pdwSize, 1);
  if ( pdwSize && (v4 = (struct _MIB_IPFORWARDTABLE *)malloc(pdwSize), (v49 = v4) != 0) )
  {
    v5 = v4;
    memset(v4, 0, pdwSize);
    if ( !GetIpForwardTable(v5, &pdwSize, 1) )
    {
      v6 = v5->dwNumEntries;
      v7 = -1;
      v45 = -1;
      v47 = 0;
      if ( v6 )
      {
        v8 = (int)&v5->table[0].dwForwardMask;
        while ( 1 )
        {
          if ( v51 > 0 && v51 != -1 )
          {
            v9 = *(_DWORD *)(v8 - 4);
            if ( v9 > 0 && v9 != -1 && *(_DWORD *)v8 > 0u )
            {
              v10 = *(_DWORD *)v8 & v9;
              if ( v10 == (v51 & *(_DWORD *)v8) )
                break;
            }
          }
          if ( !*(_DWORD *)(v8 - 4) && !*(_DWORD *)v8 && (v7 == -1 || *(_DWORD *)(v8 + 32) < v7) )
          {
            v7 = *(_DWORD *)(v8 + 32);
            v45 = v47;
          }
          ++v47;
          v8 += 56;
          if ( v47 >= v6 )
          {
            v11 = L"NetDetect";
            v12 = L"func";
            goto LABEL_23;
          }
        }
        v40 = *(__int32 **)v8;
        v39 = (int *)&v44;
        v13 = Util::Network::IPToString(v10, v7);
        v38 = *(_DWORD *)(v8 - 4);
        v14 = (__int32 **)v13;
        v37 = &v45;
        v17 = (int **)Util::Network::IPToString(v16, v15);
        v40 = *v14;
        v39 = *v17;
        v38 = v18;
        Util::Network::IPToString(v18, v19);
        v11 = L"NetDetect";
        v12 = L"func";
        sub_67DEBB47(
          (int)L"file",
          240,
          (int)L"func",
          3,
          L"NetDetect",
          (const char *)L"dwTargetIP[%s] forwardRaw.dwForwardDest[%s] forwardRaw.dwForwardMask[%s]",
          v38);
        CTXStringW::~CTXStringW((CTXStringW *)&v45);
        CTXStringW::~CTXStringW((CTXStringW *)&v44);
        v45 = v47;
LABEL_23:
        if ( v45 != -1 )
        {
          v40 = (__int32 *)1;
          *(_DWORD *)a1 = 1;
          GetIpAddrTable(0, &v46, (BOOL)v40);
          if ( v46 && (v20 = malloc(v46), (pIpAddrTable = (PMIB_IPADDRTABLE)v20) != 0) )
          {
            memset(v20, 0, v46);
            if ( GetIpAddrTable(pIpAddrTable, &v46, 1) )
            {
              sub_67E2D52E((int)L"file", 272, (int)v12, 3, (int)v11, (const char *)L"%s", L"internal err");
            }
            else
            {
              v21 = (char *)v49 + 56 * v45 + 4;
              v22 = 0;
              if ( pIpAddrTable->dwNumEntries )
              {
                v23 = *((_DWORD *)v49 + 14 * v45 + 5);
                v24 = (char *)pIpAddrTable->table;
                while ( *((_DWORD *)v24 + 1) != v23 )
                {
                  ++v22;
                  v24 += 24;
                  if ( v22 >= pIpAddrTable->dwNumEntries )
                    goto LABEL_43;
                }
                if ( dword_67F5656C && *((_DWORD *)v49 + 14 * v45 + 4) != dword_67F5656C
                  || dword_68017534 && *(_DWORD *)v24 != dword_68017534 )
                {
                  v40 = *(__int32 **)v24;
                  v39 = (int *)&v41;
                  v25 = Util::Network::IPToString(v23, v22);
                  v38 = *((_DWORD *)v21 + 3);
                  v52 = (__int32 **)v25;
                  v37 = (int *)&v42;
                  v28 = (int **)Util::Network::IPToString(v27, v26);
                  v36 = dword_68017534;
                  v44 = v28;
                  v45 = Util::Network::IPToString(v30, v29);
                  v33 = (int **)Util::Network::IPToString(v32, v31);
                  v40 = *v52;
                  v39 = *v44;
                  v38 = *(_DWORD *)v45;
                  v37 = *v33;
                  v36 = v45;
                  Util::Network::IPToString(v45, v34);
                  sub_67DEBB47(
                    (int)L"file",
                    292,
                    (int)L"func",
                    2,
                    L"NetDetect",
                    (const char *)L"TargetIP=%s, PrevNextHop=%s, PrevIf=%s, CurrentNextHop=%s, CurrentIf=%s",
                    v36);
                  CTXStringW::~CTXStringW((CTXStringW *)&v47);
                  CTXStringW::~CTXStringW((CTXStringW *)&v43);
                  CTXStringW::~CTXStringW((CTXStringW *)&v42);
                  CTXStringW::~CTXStringW((CTXStringW *)&v41);
                  *(_DWORD *)a2 = 1;
                }
                v35 = *((_DWORD *)v21 + 3);
                v40 = Target;
                dword_67F5656C = v35;
                v39 = *(int **)v24;
                dword_68017534 = (int)v39;
                *(_DWORD *)a3 = v39;
                sub_67E57D1C((const wchar_t *)L"file", (int)v39, v40);
              }
            }
          }
          else
          {
            v40 = (__int32 *)L"internal err";
            sub_67E2D52E((int)L"file", 265, (int)v12, 3, (int)v11, (const char *)L"%s", L"internal err");
          }
        }
      }
      goto LABEL_43;
    }
    sub_67E2D52E((int)L"file", 222, (int)L"func", 3, (int)L"NetDetect", (const char *)L"%s", L"internal err");
  }
  else
  {
    sub_67E2D52E((int)L"file", 213, (int)L"func", 3, (int)L"NetDetect", (const char *)L"%s", L"internal err");
  }
  *(_DWORD *)a1 = 1;
LABEL_43:
  if ( v49 )
    free(v49);
  if ( pIpAddrTable )
    free(pIpAddrTable);
}

     发现QQ使用了GetIpForwardTable这个windows api,围绕GetIpForwardTable进行状态判断,经过查询,此函数说明如下:The GetIpForwardTable function retrieves the IPv4 routing table. 
    在网络上找到这样一篇文章《[Windows]如何判断指定的IP是否能访问?》  
http://www.qdac.cc/?cat=51
把代码放上:
/*判断是否能够路由到指定的IP
Parameters
   ARemote : 用来确定网卡的远程地址
Returns
   在以下情况下,返回false:
   1.无法路由到远程地址;
  2.网卡被禁用或网卡断开连接
  否则,返回true
*/
bool __fastcall CanRouteTo(const in_addr &ARemote)
    {
    MIB_IFTABLE *ATable;
    DWORD ASize=0;
    int ARetVal=0;//Unknown
    if(GetIfTable(NULL,&ASize,false)==ERROR_INSUFFICIENT_BUFFER)
        {
        ATable=(MIB_IFTABLE *)malloc(ASize);
        PMIB_IPFORWARDTABLE pIpForwardTable=NULL;
        try
            {
            if(GetIfTable(ATable,&ASize,false)==ERROR_SUCCESS)
                {
                ASize=0;
                int ALastMatric;
                if(GetIpForwardTable(pIpForwardTable, &ASize, false)==ERROR_INSUFFICIENT_BUFFER)
                    {
                    pIpForwardTable = (PMIB_IPFORWARDTABLE)malloc(ASize);
                    if(GetIpForwardTable(pIpForwardTable, &ASize, false)==ERROR_SUCCESS)
                        {
                        for(int j=0; (j<pIpForwardTable->dwNumEntries)&&(ARetVal==0); j++)
                            {
                            if((pIpForwardTable->table[j].dwForwardMask&ARemote.S_un.S_addr)==pIpForwardTable->table[j].dwForwardDest)
                                {
                                if(pIpForwardTable->table[j].dwForwardType>=3)
                                    {
                                    for(DWORD i=0; i<ATable->dwNumEntries; i++)
                                        {
                                        if(pIpForwardTable->table[j].dwForwardIfIndex==ATable->table[i].dwIndex)
                                            {
                                            switch(ATable->table[i].dwOperStatus)
                                                {
                                                case MIB_IF_OPER_STATUS_OPERATIONAL:
                                                case MIB_IF_OPER_STATUS_CONNECTED:
                                                    ARetVal=1;//Connected
                                                    break;
                                                default:
                                                    ARetVal=2;//Disconnected
                                                    break;
                                                }
                                            break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        __finally
            {
            free(ATable);
            if(pIpForwardTable)
                free(pIpForwardTable);
            }
        }
    return (ARetVal==1);
}

     经过对比网上代码和ida伪代码对比,可以发现他们使用的原理是完全相同的,
     最后总结一下QQ断网判断流程: QQ另起一定时器线程,
每一秒调用一次GetIpForwardTable这个window api,根据返回的状态判断当前是否断网,或是联网。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 3
支持
分享
最新回复 (10)
雪    币: 2
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
good good
2015-12-18 15:56
0
雪    币: 382
活跃值: (362)
能力值: ( LV2,RANK:140 )
在线值:
发帖
回帖
粉丝
3
mark.......
2015-12-18 16:12
0
雪    币: 958
活跃值: (174)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
不错,学习了.
2015-12-18 17:23
0
雪    币: 135
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
判断当前是否断网 mark
2015-12-18 20:28
0
雪    币: 2044
活跃值: (237)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
mark....
2015-12-18 21:32
0
雪    币: 281
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
nice啊
2015-12-19 20:54
0
雪    币: 341
活跃值: (143)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
8
好。。。。
2015-12-19 21:56
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
想学,但是看不懂哦
2015-12-20 10:44
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
能加你QQ嘛  学习一下
2015-12-21 17:37
0
雪    币: 83
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
顶~~学习了
2015-12-21 19:02
0
游客
登录 | 注册 方可回帖
返回
//