騰訊在其QQ免費注冊頁面http://reg.qq.com/中,為了限制用戶注冊,設置了多種限制手段,尤其是在其JS頁面中設置了多種算法,防止用戶批量注冊。
本文主要分析QQ是如何在WEB前臺實現(xiàn)防止用戶批量的注冊,并且提供了相應的技術解決方案,程序早都做好了,沒有外放,看到博客園上有其他人對外寫了這樣的文章,但是比較簡陋,因此這里將我的設計方案跟各位分析一下
首先看我的最終實現(xiàn)效果圖,比較簡陋一些,多線程實現(xiàn)的,如果有什么疑問,可以跟我聯(lián)系,本人聯(lián)系QQ:8112857.
在開始注冊前邊的框框里邊輸入想要一次性批量申請QQ號碼的數(shù)量,然后點擊開始注冊,系統(tǒng)自動的生成相應的線程,然后開始進行排隊打碼,在每個輸入框輸入相應的注冊碼以后,點擊回車,系統(tǒng)會自動的進行注冊,并跳轉到另外一個框框里邊,并將正確的QQ號碼自動保存到TXT文本里邊。系統(tǒng)我在實現(xiàn)的時候,沒有考慮最后的臨界區(qū)的問題,因此如果在沒有輸入驗證碼,并且關閉的時候,系統(tǒng)會假死,當然了,這個問題不影響使用,下面我說明下我的設計方案。
1.分析QQ注冊提交
實際上QQ注冊頁面利用JAVASCRIPT操縱和很多COOKIES信息,而且利用COOKIES信息也進行了一系列的操作,而實際上我們完全可以給屏蔽掉,將關于操縱COOKIE的所有信息都給屏蔽掉,因此就是解析來的步驟
第一步,獲取驗證碼,并且顯示出來,這里我使用的是我們公司自己的控件,PNHTTP,你們也可以使用相應的組件,譬如說MSXML之類的只要能夠實現(xiàn)GET或者POST方式的
TRegThread(aThread).Bmp:= Http.HttpBmp('http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837'); //遠程獲取驗證碼,并保存到TBITMAP里邊
第二步,用戶輸入驗證碼,及其其他的內容信息以后,還不能直接的提交,騰訊在這里對數(shù)據(jù)進行了一個加操作,首先像checkconn頁面發(fā)出一個GET申請,這個操作主要就是獲取一串JSON代碼,里邊包含了需要提交的各變量的名稱,也就是FORM里邊的INPUT變量的名稱,這個變量的名稱騰訊做的比較變態(tài),還進行了一些算法,經過分析,我給還原過來如下
獲取表單變量
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的倒數(shù)兩位
4 LBase:= HexToInt(StrCookie); ///將COOKIE倒數(shù)兩位進行十六進制轉換
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'; ///區(qū)域代碼
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解析,但是我這里嫌麻煩,所以自己用的分割函數(shù)直接處理
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]+ '&'; ///這里是構造提交數(shù)據(jù)信息
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
以上的過程就完成了騰訊的注冊流程,但是僅僅這樣是不夠的,因為我們所需要的最終目的是多線程,多線程怎么實現(xiàn)呢?我這里采用DELPHI線程池的方式
代碼
1 TCoding= record ///這里是記錄打碼區(qū)的狀態(tài)
2 Status: integer; //忙碌1,空閑0,等待用戶輸入數(shù)據(jù)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; ///判斷兩個任務是否重復,此函數(shù)必須在派生類寫明
24 function Info: string; override; ///輸出信息,覆蓋
25 end;
26
相關打碼區(qū)函數(shù)
1 var
2 MainForm: TMainForm;
3 Codings: array[1..9] of TCoding;
4 CodingCs: TPNCriticalSection; ///申請打碼資源的CS
5
6 RegId: integer;
7
8 StrLog: string; ///日志數(shù)據(jù)
9 PoolReg: TPNThreadPool; ///線程池
10 csLog: TPNCriticalSection; ///保存日志的臨界區(qū)
11
12 function CodingApply: integer; //申請打碼顯示資源,如果申請成功,返回顯示的標號,否則返回-1
13 function CodingRelease(CodeIdx: Integer): string; //釋放顯示資源,返回的是打碼的信息
14 function CodingWait(CodeIdx: Integer): Boolean; //將狀態(tài)更改為等待
15 function CodingOK(CodeIdx: Integer): Boolean; //將狀態(tài)更改為處理完畢
16 function CodingStatus(CodeIdx: Integer): integer; //獲取當前狀態(tài)
17
具體的線程池設置代碼,對于申請打碼區(qū)資源,及其釋放打碼區(qū)資源,都寫得有具體的方案
代碼
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+'數(shù)據(jù)日志.txt','返回的參數(shù)集:'+ FormParams,False);
210 // FormParams:= Copy(FormParams,33,402);
211 StrCookie:= Http.CookieMgr.CookieCollection.Cookie['PCCOOKIE','qq.com'].Value;
212 // FPNWriteLnText(TRegData(aDataObj).FId+'數(shù)據(jù)日志.txt','PCCOOKIE值為:'+ StrCookie,False);
213 StrCookie:= copy(StrCookie,length(StrCookie)-1,2);
214 // FPNWriteLnText(TRegData(aDataObj).FId+'數(shù)據(jù)日志.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+'數(shù)據(jù)日志.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+'數(shù)據(jù)日志.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+'數(shù)據(jù)日志.txt','提交COOKIE為:'+ StrCookie,False);
267 // FPNWriteLnText(TRegData(aDataObj).FId+'數(shù)據(jù)日志.txt','提交參數(shù)為:'+ FormParams,False);
268 // FPNWriteLnText(TRegData(aDataObj).FId+'返回數(shù)據(jù).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; ///線程處理函數(shù)
308 AdjustInterval := 5 * 1000; ///減少線程間隔時間,5秒
309 MinAtLeast := False; ///是否設置最小線程數(shù)
310 ThreadDeadTimeout := 10 * 1000; ///線程死亡超時時間
311 ThreadsMinCount := 10; ///最小線程數(shù)
312 ThreadsMaxCount := 50; ///最大線程數(shù)
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,比較變態(tài)一些,但是感覺騰訊做的還不完善。