圣诞礼物——突破Netkeeper开wifi限制

原文来自Tracy'Blog——【圣诞礼物——突破Netkeeper开wifi限制

在用SF飘逸的赢下一局后,果断退出游戏,选定11+war3,然后shift+delete。从此似乎世界清静了好多,效率也高多了,于是,就有了这篇文章。今天12/24平安夜,明天就是圣诞节了,顿时感觉自己像是经历了各种沧桑后对这些节日压根就提不起任何兴趣了。还记得大一大二还在满校园跑着送苹果来着,可现在却感觉将这一切都看淡了一般,节日,似乎并没什么特别。写着文章时,习惯性点开douban.fm结果第一首歌就是MerryChristmas。好吧,虽然已没有那般冲动为各位送上苹果。那,写下这篇文章,就当送给所有用Netkeeper客户端却想开wifi的孩子吧。嗯,这是送给你的圣诞礼物!还记得去年的圣诞节,还坐在图书馆平平安安的看了一平安夜的计算机网络呢~~额,不扯远了。
  回归正文来聊聊Netkeeper吧,想要解除限制,我们首先得知道它做了些什么,也即是分析这个客户端是干嘛的。我们来推断一下吧:
  

引用:
1、拨号上网(这也是它对我们来说的主要功能了,我们装这个软件不就是为了上网么?)
  2、为什么win自带拨号功能,还要个客户端呢?(限制一:防止我们用win自带的拨号功能)
  3、为什么要限制呢?(因为win提供的拨号程序,可以实现共享,那就不能限制每人只用一个账号了,这样的话,每个人就要去开账号,他赚的就多了,而且,附加一些其他的功能,比如广告推送可以赚宣传费、比如流量控制、比如方便网络管理、比如消息控制、比如……)
  4、怎么限制呢?
    a、win自带客户端,程序界面中不能输入特殊符号,可用特殊字符来限制。(安郎客户端就是这么做的)
    b、用户从ISP手中得到的账号密码,并不是真实的拨号用的账号密码,真实的账号密码可能是这个账号或者密码,通过一种特定的加密算法加密后所生成的。(绝大多数拨号客户端的做法)
    c、用户得到的账号密码是真实的,不过,在于pppoe服务器验证时,却附带了其他条件,如netkeeper的动态用户名。
    d、我还没遇到其他的,暂且不分析……

  从上面的分析来看,整个客户端,我们想要的,也就只有第一项了,其余的都是附加的~。那,我们要做的事,无非就是还原回去,去掉这些其他的限制,直接使用win拨号,如此一来我们也就可以破除其所谓的限制了。我们可以开wifi、用路由,可以毫无限制了。(当然,心跳包的事待会儿会提到)
  那,我们要做的事就是分析一下我们的客户端是怎么实现限制的吧。记得来重邮知道netkeeper这个玩意之后就做过一件事,用以前的hook工具,看真实密码,结果发现程序在我们的用户名前加了一些字符,然后多试了几次,发现,居然每次添加的字符都是不同的。顿时在想,这玩意儿莫不是用的动态用户名?那,原理呢?时间?还是传说中的伪随机数生成算法?
  实践告诉我们,不懂得问问度娘、问问谷歌。一搜索,发现是用的动态用户名,那他是怎么生成的呢?在分析完本地密码保存后,一直有个想法,去自己分析心跳包、和这个动态用户名生成算法。可,想法终归还只是想法,到现在都没去真正分析过。之后,因为怕大家用工具共享wifi,于是,netkeeper在每次拨号的时候建立一个属于自己的拨号实体ChainNetSNWide,并且在每次断开连接的时候,删除掉这个实体。目的是什么呢?就是不让你设置让它共享。因为你的设置必须在非连接状态。而这个ChainNetSNWide也只在建立连接后才能操作的。
  之后,搜索找到了动态用户名的生成算法js脚本,和linux下的openkeeper,既然有linux下的东西,那就说明可能能找到源码,再就是有js脚本,和后来搜索得到的win下的用户名生成器源码。就开始了这次写自己的拨号工具的过程了。
  首先了解下这个动态用户名生成算法,核心就是:
  

代码:
realusername = "\r\n" & 算法b(格式符计算) & (MD5(算法a(时间) + m_username.Left(m_username.FindOneOf("@")) + RADIUS)).Left(2) & username;

  也就是,在生成的用户名中,前两个字符时固定的\r\n,之后通过算法b得到四位格式符,然后再把通过算法a作用于时间得到的结果与用户名中“@”符号前的字符与拨号因子RADIUS连接起来,之后用MD5对其散列,并取其左两位字符,之后把前面得到的这8个字符连接我们输入的用户名,便得到了真实的用户名。
  好吧,分析阶段到这里了。下面开始看代码吧,由于不是我写的,我只是改了改,让他能够为我所用而已,所以,我只是简短的用他的代码,阐述一下这个动态用户名的生成过程。(由于没学过C++、linux下的C编程等等,移植程序还是花了点时间)
  移植到win下的,用VC++ 6.0能编译的主要用户名生成代码如下:

代码:
CString CXKUsername::Realusername()
{
  time_t m_time;            //得到系统时间,从1970.01.01.00:00:00 开始的秒数
  long m_time1c;            //时间初处理m_time1c为结果,经过时间计算出的第一次加密
  long m_time1convert;        //对时间操作后的结果,此为格式字串的原始数据
  unsigned char ss[4] =
  {
    0,0,0,0
  };    //源数据1,对m_time1convert进行计算得到格式符源数据
  unsigned char ss2[4] =
  {
    0,0,0,0
  };    //md5加密参数的一部分,m_time1c的字符形式
  CString strS1;            //md5加密参数的一部分,ss2的整体形式
  CString m_formatsring;        //由m_timece算出的字符串,一般为可视字符
  CString m_md5;            //对初加密(m_timec字符串表示+m_username+radius)的MD5加密
  CString m_md5use;          //md5 Lower模式的前两位



  //取得系统时间m_time
  time(&m_time);
  //时间初处理m_time1c为结果,经过时间计算出的第一次加密
  //子函数////////////////////////////
  {
    LONG64 t;
    t = m_time;
    t *= 0x66666667;
    t >>= 0x20;
    t >>= 0x01;
    m_time1c = (long) t;
  }
  //5秒内动态用户名一致处理
  if (m_time1c <= m_lasttimec)
  {
    m_time1c = m_lasttimec + 1;
  }
  m_lasttimec = m_time1c;
  {
    long t;
    t = m_time1c;
    ss2[3] = (t & 0xFF);
    ss2[2] = (t & 0xFF00) / 0x100  ;
    ss2[1] = (t & 0xFF0000) / 0x10000;
    ss2[0] = (t & 0xFF000000) / 0x1000000;
    {
      //strS1必须用自加得到,直接加出问题
      for (int i = 0; i < 4; i++)
      {
        strS1 += ss2[i];
      }
    }
  }

  /////////////////////////////////////
  //倒置过程m_time1convert为结果
  //子函数////////////////////////////
  {
    int t, t1, t2, t3;
    t = m_time1c;
    t1 = t;
    t2 = t;
    t3 = t;
    t3 = t3 << 0x10;
    t1 = t1 & 0x0FF00;
    t1 = t1 | t3;
    t3 = t;
    t3 = t3 & 0x0FF0000;
    t2 = t2 >> 0x10;
    t3 = t3 | t2;
    t1 = t1 << 0x08;
    t3 = t3 >> 0x08;
    t1 = t1 | t3;
    m_time1convert = t1;
  }
  /////////////////////////////////////

  //源数据1,对m_time1convert进行计算得到格式符源数据
  //子函数////////////////////////////
  {
    long t;
    t = m_time1convert;
    ss[3] = (t & 0xFF);
    ss[2] = (t & 0xFF00) / 0x100  ;
    ss[1] = (t & 0xFF0000) / 0x10000;
    ss[0] = (t & 0xFF000000) / 0x1000000;
  }
  /////////////////////////////////////

  //格式符初加密
  unsigned char pp[4] =
  {
    0,0,0,0
  };
  //子函数////////////////////////////
  {
    int i = 0, j = 0, k = 0;
    for (i = 0; i < 0x20; i++)
    {
      j = i / 0x8;
      k = 3 - (i % 0x4);
      pp[k] *= 0x2;
      if (ss[j] % 2 == 1)
      {
        pp[k]++;
      }
      ss[j] /= 2;
    }
  }
  /////////////////////////////////////
  //格式符计算,m_formatsring为结果
  unsigned char pf[6] =
  {
    0,0,0,0,0,0
  };
  //子函数////////////////////////////
  {
  if(sizeof(int)==2){
    int t1, t2;
    t1 = pp[3];
    t1 /= 0x4;
    pf[0] = t1;
    t1 = pp[3];
    t1 = t1 & 0x3;
    t1 *= 0x10;
    pf[1] = t1;
    t2 = pp[2];
    t2 /= 0x10;
    t2 = t2 | t1;
    pf[1] = t2;
    t1 = pp[2];
    t1 = t1 & 0x0F;
    t1 *= 0x04;
    pf[2] = t1;
    t2 = pp[1];
    t2 /= 0x40;
    t2 = t2 | t1;
    pf[2] = t2;
    t1 = pp[1];
    t1 = t1 & 0x3F;
    pf[3] = t1;
    t2 = pp[0];
    t2 /= 0x04;
    pf[4] = t2;
    t1 = pp[0];
    t1 = t1 & 0x03;
    t1 *= 0x10;
    pf[5] = t1;
    }
  else{
          short t1,t2 ;
    t1 = pp[3];
    t1 /= 0x4;
    pf[0] = t1;
    t1 = pp[3];
    t1 = t1 & 0x3;
    t1 *= 0x10;
    pf[1] = t1;
    t2 = pp[2];
    t2 /= 0x10;
    t2 = t2 | t1;
    pf[1] = t2;
    t1 = pp[2];
    t1 = t1 & 0x0F;
    t1 *= 0x04;
    pf[2] = t1;
    t2 = pp[1];
    t2 /= 0x40;
    t2 = t2 | t1;
    pf[2] = t2;
    t1 = pp[1];
    t1 = t1 & 0x3F;
    pf[3] = t1;
    t2 = pp[0];
    t2 /= 0x04;
    pf[4] = t2;
    t1 = pp[0];
    t1 = t1 & 0x03;
    t1 *= 0x10;
    pf[5] = t1;
    }
  }
  /////////////////////////////////////
  {
    int i;
    for (i = 0; i < 6; i++)
    {
      pf[i] += 0x20;
      if ((pf[i]) >= 0x40)
      {
        pf[i]++;
      }
    }
  }
  {
    for (int i = 0; i < 6; i++)
    {
      m_formatsring += pf[i];
    }
  }
  /////////////////////////////////////
  CString strInput;
  char temp[100];
  strInput = strS1 + m_username.Left(m_username.FindOneOf("@")) + RADIUS;
  strcpy(temp,strInput.GetBuffer(100));
  m_md5 = MD5String(temp);
  m_md5use = m_md5.Left(2);
  m_realusername = m_formatsring + m_md5use + m_username;
  m_realusername = LR + m_realusername;//前面两位为回车换行0D0A,接着再是后续的
//#define _debug  
#ifdef _debug
cout<<"m_username.FindOneOf(\"@\"):"<<m_username.FindOneOf("@")<<endl;  
cout<<"sizeof(int):"<<sizeof(int)<<",m_formatsring:"<<m_formatsring<<endl<<"temp:"<<temp<<",m_md5:"<<m_md5<<endl<<"m_realusername:"<<m_realusername<<", m_md5use:"<< m_md5use<<endl;
#endif
  return m_realusername;
}

  本来还想善始善终的解释下这些代码的,看着看着就不想解释了,甚至连自己改了哪些都不想说了,自己看看吧。
  这样子就可以得到我们的用户名了,于是,瞬间想到直接脱离客户端,自己使用用户名和密码拨号吧。
  就写了个main函数,其作用是接受输入用户名返回真实用户名,形如:

代码:
char main (int argc,char **argv)
{
  CXKUsername real(argv[1]);
  return (LPSTR)(LPCTSTR)real.Realusername();
}

  后来想了想,里面有特殊字符不能输入怎么办?很简单啊,用rasdial命令,可是,如何用bat得到这个程序的返回值将是一个头痛的问题。
  于是,想了想,直接把拨号也整合到程序里去吧,又有了下面这个main,怕服务器端对拨号连接的实体有判定,就先复制了个ChainNetSNWide为ChainNetSNWide1,main函数形如:

代码:
int main (int argc,char **argv)
{
  CXKUsername real(argv[1]);
  char *temp = (LPSTR)(LPCTSTR)real.Realusername();
  char cmd1[]="rasdial ChainNetSNWide1 ";//构造cmd命令
  strcat(cmd1,temp);
  strcat(cmd1," ");//命令中需要空格
  strcat(cmd1,argv[2]);
  system (cmd1);
}

  可,这样做的结果是:总显示错误691,问题在哪呢?分析了半天没结果,调试了下,发现生成的动态用户名与客户端生成的一致,难不成是不能用cmd命令的?
  于是想了想,还是直接用VC调用api拨号吧,这样能保证不在某个地方出错,于是,又有了下面的main函数:

代码:
int main (int argc,char **argv)
{
  if (argc!=4)
  {
    printf("Parameter Error!\nUseage:rdial [entryname] [username] [password]\n");
    system("pause");
    exit(0);
  }
  CXKUsername real(argv[2]);
  // 同步调用方式
  RASDIALPARAMS RasDialParams;
  HRASCONN m_hRasconn;
  // 总是设置dwSize 为RASDIALPARAMS结构的大小
  RasDialParams.dwSize = sizeof(RASDIALPARAMS);
  m_hRasconn = NULL; 
  // 设置szEntryName为空字符串将允许RasDial使用缺省拨号属性
  _tcscpy(RasDialParams.szEntryName, _T(argv[1])); 
  RasDialParams.szPhoneNumber[0] =  _T('\0');
  RasDialParams.szCallbackNumber[0] = _T('\0');
  _tcscpy(RasDialParams.szUserName, _T(real.Realusername()));
  _tcscpy(RasDialParams.szPassword, _T(argv[3]));
  RasDialParams.szDomain[0] =  _T('\0');
  // 同步方式调用RasDial(第五个参数为NULL)
  DWORD Ret = RasDial(NULL, NULL, &RasDialParams, 0, NULL, &m_hRasconn);
  if (Ret != 0) 
  { 
    TCHAR szBuff[MAX_PATH];
    _stprintf(szBuff,_T("RasDial失败: Error = %d\n"), Ret);
    OutputDebugString(szBuff);
    printf (szBuff);
    return 1;
  }
  else
  {
    printf("使用 %s 连接成功\n",argv[2]);
    return 0;
  }
}

  为了方便在dos下调用,实现循环拨号,于是在成功时返回0,失败是返回1。
  就这样,我们的可以替代Netkeeper的拨号工具就打造成功了。下面就可以验证这个拨号认证过程中,服务器会不会判定拨号实体的名字了,随意建立了一个宽带连接,放上去测试,顺利连接。也就说明,没有这个限制。那么,接下来我们就可以共享网络了。其他的不说了,只说怎么开wifi吧(win8下我各种测试都没成功,所以,我又装了个win7)。
  1. 首先确认无线网卡开关开启,驱动程序已成功安装,无线网络功能可正常使用。
  鼠标点击右下角任务栏网络图标,若出现周围其他网络信号,无线网卡即为可用状态。
   
  2. 确定网络共享相关服务是否开启。
  可右击桌面“计算机”图标,依次打开“管理”---“服务和应用程序”---“服务”(或同时按下“windows键和R键”,输入“services.msc”,点击确定打开“服务管理器”),查看“Internet Connection Sharing (ICS)”和“Remote Procedure Call (RPC)”服务是否启用。若“手动”未“正在运行”,在相应服务右击选“启动”;若“禁用”,相应服务处右击选“属性”,“启动方式”设置为“自动”,点击“应用”按钮,点击“启动”,再点击“确定”,完成设置。 
   
  3. 设置承载网络。

  新建一个txt文档,记事本打开,输入netsh wlan set hostednetwork mode=allow ssid=您想建立的网络名字(英文格式) key=您想设置的密码(至少8位)按下Enter键,设置ssid和密码,换行再netsh wlan start hostednetwork。
  例如:
  netsh wlan set hostednetwork mode=allow ssid=mywlan key=12345678
  netsh wlan start hostednetwork
  之后保存,关闭记事本,改文件名“新建文本文档.txt”为“start_wifi.bat”。
  之后右键该文件,选择以管理员运行。即可开启承载网络。
   
  4. 设置网络共享

  依次打开 控制面板---查看网络状态和任务---更改适配器设置,若以上命令创建成功,会出现一个“本地连接*”后面带数字的网络连接。并且中间部分会显示所设置无线网络名称。在“以太网”右击选择“属性”,点击“共享”,按下图所示设置,点击“确定”按钮保存设置。(若使用宽带连接拨号上网,请在“宽带连接”右击选择属性,设置共享)
  5. 用我提供的rdial拨号上网。
  使用办法:新建一个txt文档,记事本打开,输入:
  rdial "你拨号的实体名" "username" "password"
  例如:rdial 宽带连接 xxxx@cqupt xxxx
  之后保存,关闭记事本,改文件名“新建文本文档.txt”为“connect.bat”并放在rdial所在的文件夹中。
  之后,双击运行即可联网。

  以上操作完成以后手机搜索创建的无线网络“mywlan”,使用密钥“12345678”连接测试。

  好吧,就敲到这了,不经意间都8000个字了。嗯,圣诞节了,祝大家节日快乐。
  这篇文章,也当是送给大家的圣诞礼物了。当然,你会说,还有心跳包的限制,的确,不过,先凑合着吧,下次我还来给大家分析分析心跳包。晚安~~

  (附件中提供rdial.exe、start_wifi.bat、connect.bat,需要cpp的联系我吧~)
——Tracy_梓朋

http://www.purpleroc.com/html/507231.html

posted @ 2014-02-23 01:52  花溪村长  阅读(659)  评论(0)    收藏  举报