首页
社区
课程
招聘
QQ批量注册,多线程申请,批量打码(源码DELPHI)
发表于: 2013-10-14 10:31 17883

QQ批量注册,多线程申请,批量打码(源码DELPHI)

2013-10-14 10:31
17883
腾讯在其QQ免费注册页面http://reg.qq.com/中,为了限制用户注册,设置了多种限制手段,尤其是在其JS页面中设置了多种算法,防止用户批量注册。

本文主要分析QQ是如何在WEB前台实现防止用户批量的注册,并且提供了相应的技术解决方案,程序早都做好了,没有外放,看到博客园上有其他人对外写了这样的文章,但是比较简陋,因此这里将我的设计方案跟各位分析一下

首先看我的最终实现效果图,比较简陋一些,多线程实现的,如果有什么疑问,可以跟我联系,本人联系QQ:537009488.

在开始注册前边的框框里边输入想要一次性批量申请QQ号码的数量,然后点击开始注册,系统自动的生成相应的线程,然后开始进行排队打码,在每个输入框输入相应的注册码以后,点击回车,系统会自动的进行注册,并跳转到另外一个框框里边,并将正确的QQ号码自动保存到TXT文本里边。系统我在实现的时候,没有考虑最后的临界区的问题,因此如果在没有输入验证码,并且关闭的时候,系统会假死,当然了,这个问题不影响使用,下面我说明下我的设计方案。

1.分析QQ注册提交

实际上QQ注册页面利用JAVASCRIPT操纵和很多COOKIES信息,而且利用COOKIES信息也进行了一系列的操作,而实际上我们完全可以给屏蔽掉,将关于操纵COOKIE的所有信息都给屏蔽掉,因此就是解析来的步骤

第一步,获取验证码,并且显示出来,这里我使用的是我们公司自己的控件,PNHTTP,你们也可以使用相应的组件,譬如说MSXML之类的只要能够实现GET或者POST方式的

TRegThread(aThread).Bmp:= Http.HttpBmp('http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837'); //远程获取验证码,并保存到TBITMAP里边

第二步,用户输入验证码,及其其他的内容信息以后,还不能直接的提交,腾讯在这里对数据进行了一个加操作,首先像checkconn页面发出一个GET申请,这个操作主要就是获取一串JSON代码,里边包含了需要提交的各变量的名称,也就是FORM里边的INPUT变量的名称,这个变量的名称腾讯做的比较变态,还进行了一些算法,经过分析,我给还原过来如下

获取表单变量

      FormParams:= Http.HttpGet('http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269'); ///获取checkconn页面内容
      StrCookie:= Http.CookieMgr.CookieCollection.Cookie['PCCOOKIE','qq.com'].Value; ///获取'PCCOOKIE'这个COOKIE里边保存的COOKIE信息
      StrCookie:= copy(StrCookie,length(StrCookie)-1,2); ///获取COOKIE的倒数两位
       LBase:= HexToInt(StrCookie); ///将COOKIE倒数两位进行十六进制转换

      ParamArray[0]:= 'QQ'; ///申请类型1
      ParamArray[1]:= 'EMAIL'; ///申请类型2
      ParamArray[2]:= 'zeze'; ///QQ昵称
      ParamArray[3]:= '0'; ///QQ性别
      ParamArray[4]:= '1985'; ///出生年
      ParamArray[5]:= '1'; ///出生月
      ParamArray[6]:= '2'; ///出生日
      ParamArray[7]:= '1'; ///忘记了
      ParamArray[8]:= '2'; ///忘记了
      ParamArray[9]:= 'abc111111'; ///密码
      ParamArray[10]:= 'abc111111'; ///重复密码
      ParamArray[11]:= '1'; ///国家代码
      ParamArray[12]:= '11'; ///省份代码
      ParamArray[13]:= '1'; ///区域代码
      ParamArray[14]:= RndStr; ///验证码

     try
        SListA:= FPNSplit(Copy(FormParams,33,402),',');
        SListB:= FPNSplit(Copy(FormParams,447,64),',');
        ///上边的是处理CHECKCONN页面的内容,实际上是JSON格式的,可以直接采用JSON解析,但是我这里嫌麻烦,所以自己用的分割函数直接处理
        FormParams:= ''; ///需要提交的变量名
        ///下边的是对CHECKCONN返回内容的解密算法
        for i := 0 to 12 do begin
          IdxA:= StrToInt(SListB[i]) xor LBase;
          IdxB:= 12-i;
          IdxA:= IdxA xor 6818;
          IdxA:= IdxA xor 8315;
          IdxA:= IdxA xor 5123;
          IdxA:= IdxA xor 2252;
          for j := 0 to 5 do
            IdxA:= IdxA xor 0;
          IdxA:= IdxA mod 15;

          FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ '='+ ParamArray[IdxA]+ '&'; ///这里是构造提交数据信息
        end;

      finally
        SListA.Free;
        SListB.Free;
      end;

上边通过FormParams变量,将所需要提交的信息保存了下来,接下来我们开始向服务器提交.
提交注册,并检测结果

      StrResult:= Http.HttpPost('http://reg.qq.com/cgi-bin/getnum',FormParams,True);
      Reg:= TPerlRegEx.Create(nil);
      try
        Reg.Subject:= StrResult;
        Reg.RegEx:= '您获得的号码为:\<span id\=\"aq\-uin\" class\=\"number\">([\s\S]*?)\<';
        if Reg.MatchAgain then begin
          StrQQ:= Reg.SubExpressions[1];
          FPNWriteLnText('注册成功的QQ.txt',StrQQ,False);
        end else begin
          FPNWriteLnText('注册失败线程.txt',TRegData(aDataObj).FId,False);
        end;
      finally
        Reg.Free;
      end;

以上的过程就完成了腾讯的注册流程,但是仅仅这样是不够的,因为我们所需要的最终目的是多线程,多线程怎么实现呢?我这里采用DELPHI线程池的方式
代码

    TCoding= record ///这里是记录打码区的状态
    Status: integer;  //忙碌1,空闲0,等待用户输入数据2,用户已经输入,等待处理3
    ShowBegin: integer; //开始显示验证码的时间
  end;

  ///线程池中的线程处理类,可以派生,也可以不用派生
  TRegThread = class(TPNPoolThread)
  private
    MyCodeIdx: integer;
    bmp: TBitmap;
    procedure ShowImg1;
    procedure ShowImg;
  public
    destructor Destroy;
  end;

  TRegData= class(TPNTaskObject)
  private
    FId: String; //编号
  public
    constructor Create(const AId: string);
    function Duplicate(DataObj: TPNTaskObject;
      const Processing: Boolean): Boolean;  ///判断两个任务是否重复,此函数必须在派生类写明
    function Info: string; override;  ///输出信息,覆盖
  end;

相关打码区函数

var
  MainForm: TMainForm;
  Codings: array[1..9] of TCoding;
  CodingCs: TPNCriticalSection; ///申请打码资源的CS

  RegId: integer;

  StrLog: string; ///日志数据
  PoolReg: TPNThreadPool;  ///线程池
  csLog: TPNCriticalSection;  ///保存日志的临界区

function CodingApply: integer;  //申请打码显示资源,如果申请成功,返回显示的标号,否则返回-1
function CodingRelease(CodeIdx: Integer): string;  //释放显示资源,返回的是打码的信息
function CodingWait(CodeIdx: Integer): Boolean; //将状态更改为等待
function CodingOK(CodeIdx: Integer): Boolean; //将状态更改为处理完毕
function CodingStatus(CodeIdx: Integer): integer; //获取当前状态

具体的线程池设置代码,对于申请打码区资源,及其释放打码区资源,都写得有具体的方案

代码

function CodingApply: integer;
var
  i: integer;
begin
  CodingCs.Enter;
  Result:= -1;
  try
    for i := 1 to 9 do begin
      if Codings[i].Status=0 then begin
        Result:= i;
        Codings[i].Status:= 1;
        Break;
      end;
    end;
  finally
    CodingCs.Leave;
    Sleep(0);
  end;
end;

function CodingRelease(CodeIdx: Integer): string;
begin
  if (CodeIdx<0) or (CodeIdx>9) then Exit;
  CodingCs.Enter;
  try
    try
      Result:= TEdit(MainForm.FindComponent('Input'+ IntToStr(CodeIdx))).Text;
    except
      Result:= '';
    end;
    Codings[CodeIdx].Status:= 0;
  finally
    CodingCs.Leave;
    Sleep(0);
  end;
end;

function CodingWait(CodeIdx: Integer): Boolean;
begin
  if (CodeIdx<0) or (CodeIdx>9) then Exit;
  Result:= True;
  CodingCs.Enter;
  try
    try
      Codings[CodeIdx].Status:= 2;
    except
      Result:= False;
    end;
  finally
    CodingCs.Leave;
    Sleep(0);
  end;
end;

function CodingOK(CodeIdx: Integer): Boolean;
begin
  if (CodeIdx<0) or (CodeIdx>9) then Exit;
  Result:= True;
  CodingCs.Enter;
  try
    try
      Codings[CodeIdx].Status:= 3;
    except
      Result:= False;
    end;
  finally
    CodingCs.Leave;
    Sleep(0);
  end;
end;
function CodingStatus(CodeIdx: Integer): integer;
begin
  if (CodeIdx<0) or (CodeIdx>9) then Exit;
  CodingCs.Enter;
  try
    Result:= Codings[CodeIdx].Status;
  finally
    CodingCs.Leave;
    Sleep(0);
  end;
end;

constructor TRegData.Create(const AId: string);
begin
  FId:= AId;
end;

function TRegData.Duplicate(DataObj: TPNTaskObject;
  const Processing: Boolean): Boolean;
begin
  Result := (not Processing) and
    (FId = TRegData(DataObj).FId);
end;

function TRegData.Info: string;
begin
  Result:= 'FId='+ FId+ ';';
end;

procedure TRegThread.ShowImg1;
begin
  try
    TImage(MainForm.FindComponent('Img'+ IntToStr(MyCodeIdx))).Picture.Assign(bmp);
    TEdit(MainForm.FindComponent('Input'+ IntToStr(MyCodeIdx))).Text:= '';
  except
  end;
  CodingWait(MyCodeIdx);
end;
procedure TRegThread.ShowImg;
begin
  Synchronize(ShowImg1);
end;
destructor TRegThread.Destroy;
begin
  try
    if bmp<>nil then bmp.Free;
  except
  end;
  inherited Destroy;
end;

function HexToInt(const S: String): DWORD;
asm
  PUSH EBX
  PUSH ESI

  MOV ESI, EAX //字符串地址
  MOV EDX, [EAX-4] //读取字符串长度

  XOR EAX, EAX //初始化返回值
  XOR ECX, ECX //临时变量

  TEST ESI, ESI //判断是否为空指针
  JZ @@2
  TEST EDX, EDX //判断字符串是否为空
  JLE @@2
  MOV BL, $20
  @@0:
  MOV CL, [ESI]
  INC ESI

  OR CL, BL //如果有字母则被转换为小写字母
  SUB CL, '0'
  JB @@2 // < '0' 的字符
  CMP CL, $09
  JBE @@1 // '0'..'9' 的字符
  SUB CL, 'a'-'0'-10
  CMP CL, $0A
  JB @@2 // < 'a' 的字符
  CMP CL, $0F
  JA @@2 // > 'f' 的字符
  @@1: // '0'..'9', 'A'..'F', 'a'..'f'
  SHL EAX, 4
  OR EAX, ECX
  DEC EDX
  JNZ @@0
  JMP @@3
  @@2:
  XOR EAX, EAX // 非法16进制字符串
  @@3:
  POP ESI
  POP EBX
  RET
end;

procedure TMainForm.DownProcessRequest(Sender: TPNThreadPool;
  aDataObj: TPNTaskObject; aThread: TPNPoolThread);
var
  Http: TPNHttp;
  i,j,LBase,IdxA,IdxB: integer;
  RndStr,FormParams,StrResult,StrQQ,StrCookie,StrIP: string;
  SListA,SListB: TStringList;
  Reg: TPerlRegEx;
  ParamArray: array[0..14] of string;
begin
//  FPNWriteLnText('日志.txt',TRegData(aDataObj).FId+'开始注册',False);
  Http:= TPNHttp.Create(nil,True,True);
  Randomize;
  StrIP:= '1.193.86.'+ IntToStr(Random(255)+ 1);
//  StrIP:= IntToStr(Random(255)+ 1)+'.'+ IntToStr(Random(255)+ 1)+'.'+ IntToStr(Random(255)+ 1)+'.'+ IntToStr(Random(255)+ 1);
  Http.Request.CustomHeaders.Add('X-Forwarded-For:'+ StrIP);
  try
    try
      Http.HttpGet('http://reg.qq.com/');
//      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','首页:'+ Http.HttpHeader,False);
      TRegThread(aThread).MyCodeIdx:= CodingApply;
      ///等待获取打码资源
      while TRegThread(aThread).MyCodeIdx=-1 do begin
        sleep(500);
        TRegThread(aThread).MyCodeIdx:= CodingApply;
      end;
      try
        TRegThread(aThread).Bmp:= Http.HttpBmp('http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837');
  //      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','验证码:'+ Http.HttpHeader,False);
        TRegThread(aThread).ShowImg;
      finally
        if TRegThread(aThread).Bmp<>nil then
          TRegThread(aThread).Bmp.Free;
      end;
      while CodingStatus(TRegThread(aThread).MyCodeIdx)=2 do begin
        Sleep(200);
      end;
      RndStr:= CodingRelease(TRegThread(aThread).MyCodeIdx);
//      FPNWriteLnText('日志.txt',TRegData(aDataObj).FId+':RndStr='+ RndStr,False);
      FormParams:= Http.HttpGet('http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269');
//      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','CheckConn:'+ Http.HttpHeader,False);
//      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','返回的参数集:'+ FormParams,False);
//      FormParams:= Copy(FormParams,33,402);
      StrCookie:= Http.CookieMgr.CookieCollection.Cookie['PCCOOKIE','qq.com'].Value;
//      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','PCCOOKIE值为:'+ StrCookie,False);
      StrCookie:= copy(StrCookie,length(StrCookie)-1,2);
//      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','LBASE值为:'+ StrCookie,False);
      LBase:= HexToInt(StrCookie);

      ParamArray[0]:= 'QQ';
      ParamArray[1]:= 'EMAIL';
      ParamArray[2]:= 'zeze';
      ParamArray[3]:= '0';
      ParamArray[4]:= '1985';
      ParamArray[5]:= '1';
      ParamArray[6]:= '2';
      ParamArray[7]:= '1';
      ParamArray[8]:= '2';
      ParamArray[9]:= 'abc111111';
      ParamArray[10]:= 'abc111111';
      ParamArray[11]:= '1';
      ParamArray[12]:= '11';
      ParamArray[13]:= '1';
      ParamArray[14]:= RndStr;
     try
        SListA:= FPNSplit(Copy(FormParams,33,402),',');
        SListB:= FPNSplit(Copy(FormParams,447,64),',');
//        FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt',Copy(FormParams,447,64),False);
        FormParams:= '';
        for i := 0 to 12 do begin
          IdxA:= StrToInt(SListB[i]) xor LBase;
          IdxB:= 12-i;
          IdxA:= IdxA xor 6818;
          IdxA:= IdxA xor 8315;
          IdxA:= IdxA xor 5123;
          IdxA:= IdxA xor 2252;
          for j := 0 to 5 do
            IdxA:= IdxA xor 0;
          IdxA:= IdxA mod 15;
//          FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','IdxA:'+ IntToStr(IdxA),False);

          FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ '='+ ParamArray[IdxA]+ '&'
        end;

//        FormParams:= Copy(SList[0],2,28)+ '=1&'+ Copy(SList[1],2,28)+ '=1&'+ Copy(SList[2],2,28)+ '=pop67579818&'
//          + Copy(SList[3],2,28)+ '=1983&'+ Copy(SList[4],2,28)+ '='+ RndStr+'&' + Copy(SList[5],2,28)+ '=1&'
//          + Copy(SList[6],2,28)+ '=lovezeze&'+ Copy(SList[7],2,28)+ '=pop67579818&'+ Copy(SList[8],2,28)+ '=0&'
//          + Copy(SList[9],2,28)+ '=1&'+ Copy(SList[10],2,28)+ '=2&'+ Copy(SList[11],2,28)+ '=11&'
//          + Copy(SList[12],2,28)+ '=1';
      finally
        SListA.Free;
        SListB.Free;
      end;
      for i := 0 to Http.CookieMgr.CookieCollection.Count - 1 do
        StrCookie:= StrCookie+ Http.CookieMgr.CookieCollection.Items[i].CookieName+ ':'
        + Http.CookieMgr.CookieCollection.Items[i].CookieText;
      StrResult:= Http.HttpPost('http://reg.qq.com/cgi-bin/getnum',FormParams,True);
//      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','POST时候:'+ Http.HttpHeader,False);
//      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','提交COOKIE为:'+ StrCookie,False);
//      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','提交参数为:'+ FormParams,False);
//      FPNWriteLnText(TRegData(aDataObj).FId+'返回数据.txt',StrResult,False);
      Reg:= TPerlRegEx.Create(nil);
      try
        Reg.Subject:= StrResult;
        Reg.RegEx:= '您获得的号码为:\<span id\=\"aq\-uin\" class\=\"number\">([\s\S]*?)\<';
        if Reg.MatchAgain then begin
          StrQQ:= Reg.SubExpressions[1];
          FPNWriteLnText('注册成功的QQ.txt',StrQQ,False);
        end else begin
          FPNWriteLnText('注册失败线程.txt',TRegData(aDataObj).FId,False);
        end;
//        FPNWriteLnText(TRegData(aDataObj).FId+'匹配结果.txt',StrResult,False);
      finally
        Reg.Free;
      end;
    except

    end;
  finally
    Http.Free;
  end;
end;

procedure TMainForm.btn1Click(Sender: TObject);
var
  i: integer;
begin
  for i := 1 to StrToInt(RegNum.Text) do begin
    RegId:= RegId+ 1;
    PoolReg.AddRequest(TRegData.Create(IntToStr(RegId)));
  end;
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  RegId:= 0;
  CodingCs:= TPNCriticalSection.Create;
  PoolReg := TPNThreadPool.Create(nil);
  with PoolReg do begin
    OnProcessRequest := DownProcessRequest; ///线程处理函数
    AdjustInterval := 5 * 1000; ///减少线程间隔时间,5秒
    MinAtLeast := False;  ///是否设置最小线程数
    ThreadDeadTimeout := 10 * 1000; ///线程死亡超时时间
    ThreadsMinCount := 10; ///最小线程数
    ThreadsMaxCount := 50;  ///最大线程数
    uTerminateWaitTime := 2 * 1000; ///挂起等待时间
  end;

end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  PoolReg.Free;
  CodingCs.Free;
end;

procedure TMainForm.Input1KeyPress(Sender: TObject; var Key: Char);
var
  CodeIdx: integer;
  ObjName: string;
begin
  if Key= Char(13) then begin
    ObjName:= (Sender as TRzEdit).Name;
    CodeIdx:= StrToInt(Copy(ObjName,6,1));
    CodingOK(CodeIdx);
    if CodeIdx=9 then
      CodeIdx:= 1
    else
      CodeIdx:= CodeIdx+ 1;
    TRzEdit(FindComponent('Input'+ IntToStr(CodeIdx))).SetFocus;
  end;
end;
本文只是对QQ注册页面进行一些分析,供大家参考,其实大家在做WEB设计的时候,如何防止批量注册这块,也可以参考下QQ,比较变态一些,但是感觉腾讯做的还不完善。

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

收藏
免费 0
支持
分享
最新回复 (15)
雪    币: 680
活跃值: (68)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
前排留名,好东西
2013-10-14 10:41
0
雪    币: 42
活跃值: (26)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
这可以吗?tx不是限制一台机器只能注册多少个的?
2013-10-14 10:47
0
雪    币: 341
活跃值: (138)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
4
验证码自动识别就完美了
2013-10-14 11:11
0
雪    币: 69
活跃值: (242)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wmg
5
验证码要是能自动识别就完美了
2013-10-14 11:31
0
雪    币: 6976
活跃值: (1467)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
6
目测要火, 楼层招租
2013-10-14 11:34
0
雪    币: 7
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
如果电脑是拨号上网,可以加入自动拨号机制
2013-10-14 11:47
0
雪    币: 212
活跃值: (23)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
思路不错 值得学习
2013-10-14 13:00
0
雪    币: 10946
活跃值: (2900)
能力值: ( LV5,RANK:71 )
在线值:
发帖
回帖
粉丝
9
验证码如何自动识别?
2013-10-14 13:12
0
雪    币: 3652
活跃值: (4222)
能力值: (RANK:215 )
在线值:
发帖
回帖
粉丝
10
目前注册不是限制IP的吗,每个IP一个小时或者几个小时内只允许申请2-3个。
2013-10-14 14:09
0
雪    币: 66
活跃值: (203)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
11
原文出处:http://www.cnblogs.com/cntlis/archive/2010/11/25/QQBatchReg.html
转载人家文章还改自己QQ,真不道德
2013-10-14 16:33
0
雪    币: 207
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
ls知道得太多了
2013-10-14 18:00
0
雪    币: 142
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
忽悠小白去吧,腾讯早就不是当年的腾讯了.
2013-10-14 18:12
0
雪    币: 7
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
本人的delphi都是他一手教过来的 所以 随便你怀疑
2013-10-15 15:25
0
雪    币: 211
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
原文出处:http://www.cnblogs.com/cntlis/archive/2010/11/25/QQBatchReg.html
转载人家文章还改自己QQ,我不怀疑(大牛我加你教我不)
2013-10-22 01:53
0
雪    币: 10822
活跃值: (3874)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
学习一下了,感谢
2013-10-28 14:19
0
游客
登录 | 注册 方可回帖
返回
//