腾讯在其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: string; override; ///输出信息,覆盖
25 end;
26
相关打码区函数
1 var
2 MainForm: TMainForm;
3 Codings: array[1..9] of 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<0) or (CodeIdx>9) then 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<0) or (CodeIdx>9) then 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<0) or (CodeIdx>9) then 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<0) or (CodeIdx>9) then 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..14] of 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(13) then 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,比较变态一些,但是感觉腾讯做的还不完善。