QQ限制WEB页面注册方法解析及其如何采用多线程技术实现QQ号码快速批量申请

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

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

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

在开始注册前边的框框里边输入想要一次性批量申请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变量的名称,这个变量的名称腾讯做的比较变态,还进行了一些算法,经过分析,我给还原过来如下

 

获取表单变量
 1       FormParams:= Http.HttpGet('http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269'); ///获取checkconn页面内容
 2       StrCookie:= Http.CookieMgr.CookieCollection.Cookie['PCCOOKIE','qq.com'].Value; ///获取'PCCOOKIE'这个COOKIE里边保存的COOKIE信息
 3       StrCookie:= copy(StrCookie,length(StrCookie)-1,2); ///获取COOKIE的倒数两位
 4        LBase:= HexToInt(StrCookie); ///将COOKIE倒数两位进行十六进制转换
 5 
 6       ParamArray[0]:= 'QQ'///申请类型1
 7       ParamArray[1]:= 'EMAIL'///申请类型2
 8       ParamArray[2]:= 'zeze'///QQ昵称
 9       ParamArray[3]:= '0'///QQ性别
10       ParamArray[4]:= '1985'///出生年
11       ParamArray[5]:= '1'///出生月
12       ParamArray[6]:= '2'///出生日
13       ParamArray[7]:= '1'///忘记了
14       ParamArray[8]:= '2'///忘记了
15       ParamArray[9]:= 'abc111111'///密码
16       ParamArray[10]:= 'abc111111'///重复密码
17       ParamArray[11]:= '1'///国家代码
18       ParamArray[12]:= '11'///省份代码
19       ParamArray[13]:= '1'///区域代码
20       ParamArray[14]:= RndStr; ///验证码
21 
22      try
23         SListA:= FPNSplit(Copy(FormParams,33,402),',');
24         SListB:= FPNSplit(Copy(FormParams,447,64),',');
25         ///上边的是处理CHECKCONN页面的内容,实际上是JSON格式的,可以直接采用JSON解析,但是我这里嫌麻烦,所以自己用的分割函数直接处理
26         FormParams:= ''///需要提交的变量名
27         ///下边的是对CHECKCONN返回内容的解密算法
28         for i := 0 to 12 do begin
29           IdxA:= StrToInt(SListB[i]) xor LBase;
30           IdxB:= 12-i;
31           IdxA:= IdxA xor 6818;
32           IdxA:= IdxA xor 8315;
33           IdxA:= IdxA xor 5123;
34           IdxA:= IdxA xor 2252;
35           for j := 0 to 5 do
36             IdxA:= IdxA xor 0;
37           IdxA:= IdxA mod 15;
38 
39           FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ '='+ ParamArray[IdxA]+ '&'///这里是构造提交数据信息
40         end;
41 
42       finally
43         SListA.Free;
44         SListB.Free;
45       end;
46 
47 
48 

 

 

上边通过FormParams变量,将所需要提交的信息保存了下来,接下来我们开始像服务器提交

 

提交注册,并检测结果
 1       StrResult:= Http.HttpPost('http://reg.qq.com/cgi-bin/getnum',FormParams,True);
 2       Reg:= TPerlRegEx.Create(nil);
 3       try
 4         Reg.Subject:= StrResult;
 5         Reg.RegEx:= '您获得的号码为:\<span id\=\"aq\-uin\" class\=\"number\">([\s\S]*?)\<';
 6         if Reg.MatchAgain then begin
 7           StrQQ:= Reg.SubExpressions[1];
 8           FPNWriteLnText('注册成功的QQ.txt',StrQQ,False);
 9         end else begin
10           FPNWriteLnText('注册失败线程.txt',TRegData(aDataObj).FId,False);
11         end;
12       finally
13         Reg.Free;
14       end;
15 

 

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

 

 

代码
 1   TCoding= record ///这里是记录打码区的状态
 2     Status: integer;  //忙碌1,空闲0,等待用户输入数据2,用户已经输入,等待处理3
 3     ShowBegin: integer; //开始显示验证码的时间
 4   end;
 5 
 6   ///线程池中的线程处理类,可以派生,也可以不用派生
 7   TRegThread = class(TPNPoolThread)
 8   private
 9     MyCodeIdx: integer;
10     bmp: TBitmap;
11     procedure ShowImg1;
12     procedure ShowImg;
13   public
14     destructor Destroy;
15   end;
16 
17   TRegData= class(TPNTaskObject)
18   private
19     FId: String; //编号
20   public
21     constructor Create(const AId: string);
22     function Duplicate(DataObj: TPNTaskObject;
23       const Processing: Boolean): Boolean;  ///判断两个任务是否重复,此函数必须在派生类写明
24     function Info: stringoverride;  ///输出信息,覆盖
25   end;
26 

 

 

 

相关打码区函数
 1 var
 2   MainForm: TMainForm;
 3   Codings: array[1..9of TCoding;
 4   CodingCs: TPNCriticalSection; ///申请打码资源的CS
 5 
 6   RegId: integer;
 7 
 8   StrLog: string///日志数据
 9   PoolReg: TPNThreadPool;  ///线程池
10   csLog: TPNCriticalSection;  ///保存日志的临界区
11 
12 function CodingApply: integer;  //申请打码显示资源,如果申请成功,返回显示的标号,否则返回-1
13 function CodingRelease(CodeIdx: Integer): string;  //释放显示资源,返回的是打码的信息
14 function CodingWait(CodeIdx: Integer): Boolean; //将状态更改为等待
15 function CodingOK(CodeIdx: Integer): Boolean; //将状态更改为处理完毕
16 function CodingStatus(CodeIdx: Integer): integer; //获取当前状态
17 

 

 

 

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

 

 

代码
  1 
  2 function CodingApply: integer;
  3 var
  4   i: integer;
  5 begin
  6   CodingCs.Enter;
  7   Result:= -1;
  8   try
  9     for i := 1 to 9 do begin
 10       if Codings[i].Status=0 then begin
 11         Result:= i;
 12         Codings[i].Status:= 1;
 13         Break;
 14       end;
 15     end;
 16   finally
 17     CodingCs.Leave;
 18     Sleep(0);
 19   end;
 20 end;
 21 
 22 function CodingRelease(CodeIdx: Integer): string;
 23 begin
 24   if (CodeIdx<0or (CodeIdx>9then Exit;
 25   CodingCs.Enter;
 26   try
 27     try
 28       Result:= TEdit(MainForm.FindComponent('Input'+ IntToStr(CodeIdx))).Text;
 29     except
 30       Result:= '';
 31     end;
 32     Codings[CodeIdx].Status:= 0;
 33   finally
 34     CodingCs.Leave;
 35     Sleep(0);
 36   end;
 37 end;
 38 
 39 function CodingWait(CodeIdx: Integer): Boolean;
 40 begin
 41   if (CodeIdx<0or (CodeIdx>9then Exit;
 42   Result:= True;
 43   CodingCs.Enter;
 44   try
 45     try
 46       Codings[CodeIdx].Status:= 2;
 47     except
 48       Result:= False;
 49     end;
 50   finally
 51     CodingCs.Leave;
 52     Sleep(0);
 53   end;
 54 end;
 55 
 56 function CodingOK(CodeIdx: Integer): Boolean;
 57 begin
 58   if (CodeIdx<0or (CodeIdx>9then Exit;
 59   Result:= True;
 60   CodingCs.Enter;
 61   try
 62     try
 63       Codings[CodeIdx].Status:= 3;
 64     except
 65       Result:= False;
 66     end;
 67   finally
 68     CodingCs.Leave;
 69     Sleep(0);
 70   end;
 71 end;
 72 function CodingStatus(CodeIdx: Integer): integer;
 73 begin
 74   if (CodeIdx<0or (CodeIdx>9then Exit;
 75   CodingCs.Enter;
 76   try
 77     Result:= Codings[CodeIdx].Status;
 78   finally
 79     CodingCs.Leave;
 80     Sleep(0);
 81   end;
 82 end;
 83 
 84 constructor TRegData.Create(const AId: string);
 85 begin
 86   FId:= AId;
 87 end;
 88 
 89 function TRegData.Duplicate(DataObj: TPNTaskObject;
 90   const Processing: Boolean): Boolean;
 91 begin
 92   Result := (not Processing) and
 93     (FId = TRegData(DataObj).FId);
 94 end;
 95 
 96 function TRegData.Info: string;
 97 begin
 98   Result:= 'FId='+ FId+ ';';
 99 end;
100 
101 
102 procedure TRegThread.ShowImg1;
103 begin
104   try
105     TImage(MainForm.FindComponent('Img'+ IntToStr(MyCodeIdx))).Picture.Assign(bmp);
106     TEdit(MainForm.FindComponent('Input'+ IntToStr(MyCodeIdx))).Text:= '';
107   except
108   end;
109   CodingWait(MyCodeIdx);
110 end;
111 procedure TRegThread.ShowImg;
112 begin
113   Synchronize(ShowImg1);
114 end;
115 destructor TRegThread.Destroy;
116 begin
117   try
118     if bmp<>nil then bmp.Free;
119   except
120   end;
121   inherited Destroy;
122 end;
123 
124 function HexToInt(const S: String): DWORD;
125 asm
126   PUSH EBX
127   PUSH ESI
128 
129   MOV ESI, EAX //字符串地址
130   MOV EDX, [EAX-4//读取字符串长度
131 
132   XOR EAX, EAX //初始化返回值
133   XOR ECX, ECX //临时变量
134 
135   TEST ESI, ESI //判断是否为空指针
136   JZ @@2
137   TEST EDX, EDX //判断字符串是否为空
138   JLE @@2
139   MOV BL, $20
140   @@0:
141   MOV CL, [ESI]
142   INC ESI
143 
144   OR CL, BL //如果有字母则被转换为小写字母
145   SUB CL, '0'
146   JB @@2 // < '0' 的字符
147   CMP CL, $09
148   JBE @@1 // '0'..'9' 的字符
149   SUB CL, 'a'-'0'-10
150   CMP CL, $0A
151   JB @@2 // < 'a' 的字符
152   CMP CL, $0F
153   JA @@2 // > 'f' 的字符
154   @@1// '0'..'9''A'..'F''a'..'f'
155   SHL EAX, 4
156   OR EAX, ECX
157   DEC EDX
158   JNZ @@0
159   JMP @@3
160   @@2:
161   XOR EAX, EAX // 非法16进制字符串
162   @@3:
163   POP ESI
164   POP EBX
165   RET
166 end;
167 
168 procedure TMainForm.DownProcessRequest(Sender: TPNThreadPool;
169   aDataObj: TPNTaskObject; aThread: TPNPoolThread);
170 var
171   Http: TPNHttp;
172   i,j,LBase,IdxA,IdxB: integer;
173   RndStr,FormParams,StrResult,StrQQ,StrCookie,StrIP: string;
174   SListA,SListB: TStringList;
175   Reg: TPerlRegEx;
176   ParamArray: array[0..14of string;
177 begin
178 //  FPNWriteLnText('日志.txt',TRegData(aDataObj).FId+'开始注册',False);
179   Http:= TPNHttp.Create(nil,True,True);
180   Randomize;
181   StrIP:= '1.193.86.'+ IntToStr(Random(255)+ 1);
182 //  StrIP:= IntToStr(Random(255)+ 1)+'.'+ IntToStr(Random(255)+ 1)+'.'+ IntToStr(Random(255)+ 1)+'.'+ IntToStr(Random(255)+ 1);
183   Http.Request.CustomHeaders.Add('X-Forwarded-For:'+ StrIP);
184   try
185     try
186       Http.HttpGet('http://reg.qq.com/');
187 //      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','首页:'+ Http.HttpHeader,False);
188       TRegThread(aThread).MyCodeIdx:= CodingApply;
189       ///等待获取打码资源
190       while TRegThread(aThread).MyCodeIdx=-1 do begin
191         sleep(500);
192         TRegThread(aThread).MyCodeIdx:= CodingApply;
193       end;
194       try
195         TRegThread(aThread).Bmp:= Http.HttpBmp('http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837');
196   //      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','验证码:'+ Http.HttpHeader,False);
197         TRegThread(aThread).ShowImg;
198       finally
199         if TRegThread(aThread).Bmp<>nil then
200           TRegThread(aThread).Bmp.Free;
201       end;
202       while CodingStatus(TRegThread(aThread).MyCodeIdx)=2 do begin
203         Sleep(200);
204       end;
205       RndStr:= CodingRelease(TRegThread(aThread).MyCodeIdx);
206 //      FPNWriteLnText('日志.txt',TRegData(aDataObj).FId+':RndStr='+ RndStr,False);
207       FormParams:= Http.HttpGet('http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269');
208 //      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','CheckConn:'+ Http.HttpHeader,False);
209 //      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','返回的参数集:'+ FormParams,False);
210 //      FormParams:= Copy(FormParams,33,402);
211       StrCookie:= Http.CookieMgr.CookieCollection.Cookie['PCCOOKIE','qq.com'].Value;
212 //      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','PCCOOKIE值为:'+ StrCookie,False);
213       StrCookie:= copy(StrCookie,length(StrCookie)-1,2);
214 //      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','LBASE值为:'+ StrCookie,False);
215       LBase:= HexToInt(StrCookie);
216 
217       ParamArray[0]:= 'QQ';
218       ParamArray[1]:= 'EMAIL';
219       ParamArray[2]:= 'zeze';
220       ParamArray[3]:= '0';
221       ParamArray[4]:= '1985';
222       ParamArray[5]:= '1';
223       ParamArray[6]:= '2';
224       ParamArray[7]:= '1';
225       ParamArray[8]:= '2';
226       ParamArray[9]:= 'abc111111';
227       ParamArray[10]:= 'abc111111';
228       ParamArray[11]:= '1';
229       ParamArray[12]:= '11';
230       ParamArray[13]:= '1';
231       ParamArray[14]:= RndStr;
232      try
233         SListA:= FPNSplit(Copy(FormParams,33,402),',');
234         SListB:= FPNSplit(Copy(FormParams,447,64),',');
235 //        FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt',Copy(FormParams,447,64),False);
236         FormParams:= '';
237         for i := 0 to 12 do begin
238           IdxA:= StrToInt(SListB[i]) xor LBase;
239           IdxB:= 12-i;
240           IdxA:= IdxA xor 6818;
241           IdxA:= IdxA xor 8315;
242           IdxA:= IdxA xor 5123;
243           IdxA:= IdxA xor 2252;
244           for j := 0 to 5 do
245             IdxA:= IdxA xor 0;
246           IdxA:= IdxA mod 15;
247 //          FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','IdxA:'+ IntToStr(IdxA),False);
248 
249           FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ '='+ ParamArray[IdxA]+ '&'
250         end;
251 
252 //        FormParams:= Copy(SList[0],2,28)+ '=1&'+ Copy(SList[1],2,28)+ '=1&'+ Copy(SList[2],2,28)+ '=pop67579818&'
253 //          + Copy(SList[3],2,28)+ '=1983&'+ Copy(SList[4],2,28)+ '='+ RndStr+'&' + Copy(SList[5],2,28)+ '=1&'
254 //          + Copy(SList[6],2,28)+ '=lovezeze&'+ Copy(SList[7],2,28)+ '=pop67579818&'+ Copy(SList[8],2,28)+ '=0&'
255 //          + Copy(SList[9],2,28)+ '=1&'+ Copy(SList[10],2,28)+ '=2&'+ Copy(SList[11],2,28)+ '=11&'
256 //          + Copy(SList[12],2,28)+ '=1';
257       finally
258         SListA.Free;
259         SListB.Free;
260       end;
261       for i := 0 to Http.CookieMgr.CookieCollection.Count - 1 do
262         StrCookie:= StrCookie+ Http.CookieMgr.CookieCollection.Items[i].CookieName+ ':'
263         + Http.CookieMgr.CookieCollection.Items[i].CookieText;
264       StrResult:= Http.HttpPost('http://reg.qq.com/cgi-bin/getnum',FormParams,True);
265 //      FPNWriteLnText(TRegData(aDataObj).FId+'HEADER信息.txt','POST时候:'+ Http.HttpHeader,False);
266 //      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','提交COOKIE为:'+ StrCookie,False);
267 //      FPNWriteLnText(TRegData(aDataObj).FId+'数据日志.txt','提交参数为:'+ FormParams,False);
268 //      FPNWriteLnText(TRegData(aDataObj).FId+'返回数据.txt',StrResult,False);
269       Reg:= TPerlRegEx.Create(nil);
270       try
271         Reg.Subject:= StrResult;
272         Reg.RegEx:= '您获得的号码为:\<span id\=\"aq\-uin\" class\=\"number\">([\s\S]*?)\<';
273         if Reg.MatchAgain then begin
274           StrQQ:= Reg.SubExpressions[1];
275           FPNWriteLnText('注册成功的QQ.txt',StrQQ,False);
276         end else begin
277           FPNWriteLnText('注册失败线程.txt',TRegData(aDataObj).FId,False);
278         end;
279 //        FPNWriteLnText(TRegData(aDataObj).FId+'匹配结果.txt',StrResult,False);
280       finally
281         Reg.Free;
282       end;
283     except
284 
285     end;
286   finally
287     Http.Free;
288   end;
289 end;
290 
291 procedure TMainForm.btn1Click(Sender: TObject);
292 var
293   i: integer;
294 begin
295   for i := 1 to StrToInt(RegNum.Text) do begin
296     RegId:= RegId+ 1;
297     PoolReg.AddRequest(TRegData.Create(IntToStr(RegId)));
298   end;
299 end;
300 
301 procedure TMainForm.FormCreate(Sender: TObject);
302 begin
303   RegId:= 0;
304   CodingCs:= TPNCriticalSection.Create;
305   PoolReg := TPNThreadPool.Create(nil);
306   with PoolReg do begin
307     OnProcessRequest := DownProcessRequest; ///线程处理函数
308     AdjustInterval := 5 * 1000///减少线程间隔时间,5
309     MinAtLeast := False;  ///是否设置最小线程数
310     ThreadDeadTimeout := 10 * 1000///线程死亡超时时间
311     ThreadsMinCount := 10///最小线程数
312     ThreadsMaxCount := 50;  ///最大线程数
313     uTerminateWaitTime := 2 * 1000///挂起等待时间
314   end;
315 
316 end;
317 
318 procedure TMainForm.FormDestroy(Sender: TObject);
319 begin
320   PoolReg.Free;
321   CodingCs.Free;
322 end;
323 
324 procedure TMainForm.Input1KeyPress(Sender: TObject; var Key: Char);
325 var
326   CodeIdx: integer;
327   ObjName: string;
328 begin
329   if Key= Char(13then begin
330     ObjName:= (Sender as TRzEdit).Name;
331     CodeIdx:= StrToInt(Copy(ObjName,6,1));
332     CodingOK(CodeIdx);
333     if CodeIdx=9 then
334       CodeIdx:= 1
335     else
336       CodeIdx:= CodeIdx+ 1;
337     TRzEdit(FindComponent('Input'+ IntToStr(CodeIdx))).SetFocus;
338   end;
339 end;
340 

 

 

本文只是对QQ注册页面进行一些分析,供大家参考,其实大家在做WEB设计的时候,如何防止批量注册这块,也可以参考下QQ,比较变态一些,但是感觉腾讯做的还不完善。

 

posted on 2010-11-25 04:07  cntlis  阅读(8570)  评论(25编辑  收藏  举报

导航