امروز می‌خواهیم یک تمرینِ عملی از بات‌ها را اجرا کنیم؛ این‌بار هدفمان سایتِ «قلم‌چی» است و پروژه‌ای که اجرا می‌کنیم بسیار کارآمد است؛ حداقل خودِ من ابتدا آن‌را به عنوانِ یک نیاز اجرا کردم و سپس تصمیم به نوشتنِ این پست گرفتم.

توصیفِ نیاز

ابتدا به سایتِ قلم‌چی بروید. از منوی ناوبریِ بالا «مقطع شما» را انتخاب کنید، سپس یک مقطع، برای مثال «چهارم ریاضی»(که من باشم :دی) را انتخاب کنید، به صفحه‌ای مانندِ این منتقل خواهید شد.
اگر کمی با صفحه ور بروید، خواهید یافت که هر آزمونی که قلم‌چی آن‌را به تازگی برگزار کرده است از این‌جا قابلِ انتخاب است و کلیدِ آزمون‌ها در زبانه‌ی مخصوص به آن‌ها قابلِ دیدن است.
کلیدها و لیستِ آزمون‌ها
با کلیک کردنِ روی «کلید آزمون...» می‌توانیم به صفحه‌ای این‌چنینی وارد شویم. در این صفحه‌ی این‌چنینی با کلیک روی هر سوال می‌توانیم صورتِ آن‌را مشاهده کرده و پاسخ‌تشریحیِ آن‌را نیز نگاه کنیم.
سوال و پاسخِ تشریحی
خوب.
اگر انسانِ طماعی باشید احتمالاً متوجهِ هدفمان شده‌اید؛ اما مشخص‌تر می‌گویم، هدفِ ما این است که یک تاریخ آزمون به یک روبات بدهیم، سپس همه‌ی سوالات و پاسخ‌ها را به صورتِ دسته‌بندی شده از آن تحویل بگیریم؛ چیزی مانندِ این:
سوالاتی که با باتمان به دست آمده‌اند
ما صرفاً برای جنبه‌ی آموزشی‌اش این پست را دنبال می‌کنیم، اما شاید توجیهِ مالی‌ای هم پشتِ قضیه نهفته باشد(هرچند که من آن‌را دنبال نکردم، اگر توانستید پروژه را بفروشید می‌توانید به ما هم Donate کنید تا از زیرِ یوغِ محدودیت‌های بلاگِ بیان درآییم).
* لازم به ذکر است که این پروژه را با Python خواهیم نوشت.

شروعِ کار

خوب. برای شروعِ کار طبیعتاً ابتدا BurpSuite را آتیش می‌کنیم. می‌خواهیم ببینیم وقتی روی یک کلید کلیک می‌کنیم و تصویرِ سوال و پاسخش می‌آید چه درخواست‌هایی رد و بدل می‌گردد.
(بعداً خواهیم فهمید که خیلی نیاز به BurpSuite نبوده است، اما آتیش کردنِ Burp به ما یک نکته آموزش خواهد داد!)

در ویدئوی بالا می‌بینید که وقتی درخواست‌ها را فوروارد می‌کنیم، بعد از چند ثانیه از لیستِ هیستوریِ Burp حذف می‌شوند؛ این در نگاهِ اول برایم تعجب‌آورناک بود، اما در ادامه با حرکتی پارتیزانی توانستم منبعِ این ناهنجاری را بیابم. اگر دقت کنید وقتی Responseــِ یک درخواست می‌آید، از لیست حذف می‌شود، لذا باید مسئله در Response باشد؛ پس من آمدم وقتی درخواست می‌خواست به سمتِ سرور برود، آن‌را Send to Repeater کردم.
ارسال به Repeater
اکنون می‌توانیم با خیالِ نسبتاً راحت‌تری درخواست را موشکافی کنیم؛ بدونِ این‌که حذف شود و نتوانیم پاسخ را ببینیم.
به زبانه‌ی Repeater می‌رویم و درخواست را با استفاده از کلیدِ Go تکرار می‌کنیم:
مشکل یافت شد!
آنچه در پاسخِ سرور رنگی کرده‌ام، عبارت است از مشکلی که باعث می‌شد پاسخ‌ها پس از آمدن، از History در BurpSuite حذف شوند. (Burp به این خزعبلاتی که از سرور می‌آید احترام می‌گذارد...)
پس متوجه شدیم سازندگانِ سایت خیلی به sniffشدن علاقمند نیستند؛ اوکی. ما هم که اسنیف نمی‌کنیم، صرفاً برنامه‌ای آموزشی را روی سایت بررسی می‌کنیم... :)

حال برمی‌گردیم به صفحه‌ی سوال و پاسخ.
هر یک از سوالات و پاسخ‌ها «یک تصویر» است؛ لذا src دارد، با کلیک راست کردن روی آن و کلیک روی Copy image address در کروم، منبعِ آن را کپی می‌کنم که به این‌صورت است:
http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/1/Question/154&dd=R1EZgzuBR+K8K/rEr9Xp5w==

برای الگویابی چندتا دیگر را نیز از همین آزمون و همان پیج تست می‌کنم:

http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/1/Question/154&dd=R1EZgzuBR+K8K/rEr9Xp5w==
http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/1/Question/106&dd=R1EZgzuBR+K8K/rEr9Xp5w==
http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/1/Question/254&dd=R1EZgzuBR+K8K/rEr9Xp5w==

خوب. ظاهراً برای سوالات فقط یک عدد را در URL عوض کنی کار تمام است؛ یعنی فقط یک for بزنیم تا از ۱ تا تعداد سوالات پیش برود و تصاویر را دانلود کند.

But wait!

ما قرار بود به برنامه‌مان تاریخِ آزمون را بدهیم و برنامه سوالات را دانلود کند، اما این لینک‌ها چه ربطی به تاریخِ آزمون (که 13941109 است) دارند؟

سوالِ جالبی است و لازم است تست‌های بیشتری برای پاسخ‌دادن به آن طی شود.

لذا پیج را رفرش می‌کنیم و دوباره سعی می‌کنیم لینکِ چند سوال را بررسی کنیم.

بعد از رفرشِ اول:

http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/1/Question/1&dd=X35WmZvhZUE6QjxdMIOLWg==

بعد از رفرشِ دوم:

http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/1/Question/1&dd=n4j3TWaFuMCU3p3KSoCEBQ==

...

هوم... ظاهراً الگویِ مشخص و یک‌طرفه‌ای بینِ «تاریخِ آزمون» و «آن مقدارِ dd» وجود ندارد و یک مقدار Randomـی چیزی موجود است. (از طرفی اگر سعی کنید مقدارِ dd را base64decode کنید نیز به چیزِ آن‌چنان واضحی نمی‌رسید)

خوب. چه می‌توان کرد؟

به نظرِ من بیاییم و ببینیم مرورگر این مقدارِ dd را از کجا می‌آورد، ما هم آن‌را از همان‌جا جستجو کنیم. لینکی که ما برای دیدنِ کلیدِ یک آزمون استفاده می‌کنیم چیزی مشابهِ این است:

http://www.kanoon.ir/Public/TestKey.aspx?td=13941109&gc=1

یعنی با تاریخِ آزمون یک رابطه‌ی مشخصی دارد. حال بیایید سعی کنیم آن dd را در سورسِ صفحه(اولین جایی که ذهن به سمتش می‌رود) جستجو کنیم.

مقدارِ dd در سورس موجود است

خوب. ظاهراً dd را می‌توان در سورس یافت. پس یک رگکس به شکلِ زیر کارمان را راه خواهد انداخت:

p = re.compile(ur'id="CPCM_CPC_hdDate"[^\/]+value="([^"]+)"')

(زبان همان‌طور که گفتم پایتون است)

یعنی به عنوانِ یک گروه، مقداری که به دنبالِ آن‌هستیم را از سورسِ صفحه جدا می‌کند.

خوب. جمع‌بندی می‌کنیم:

۱- ما لینکِ صفحه‌ی کلیدِ یک آزمون را داریم، چرا که تاریخش را می‌دانیم:

http://www.kanoon.ir/Public/TestKey.aspx?td=13941109&gc=1

* تنها مقدارِ متغیرِ دیگر غیر از تاریخِ آزمون، gc است که رشته و پایه را مشخص می‌کند، برای مثال gc=3 یعنی رشته‌ی تجربی و کلاس چهارم. (این دیگر فرمول ندارد و باید تک‌تک به دست بیاید)

۲- از طریقِ سورسِ صفحه می‌توانیم مقدارِ ddـی که برای مشاهده‌ی تصویرِ یک سوال لازم است را بیابیم.

http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/3/Question/357&dd=HV45y6ZtIJq4jnLLJGYhNw==

۳- تنها یک for کافیست تا سوالات از ۱ تا فلان دانلود شوند!


* لینکِ پاسخ‌ها نیز کاملاً مشابه است و به فرمِ زیر می‌باشد:

http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/3/Answer/460&dd=HV45y6ZtIJq4jnLLJGYhNw==

کدزنیسم!

import requests
import re
import shutil
import os
import sys
#1394-10-10


class_code = '1' # 1 is for 4th grade, mathematics and physics, I'm too fat to list all possible scenarios, DIY
# eg: class_code = '3' is for 4th grade, practical sciences...

date = raw_input("Enter exam date(yyyy-mm-dd): ")
lower_bound = int(raw_input("Starting question number: "))
upper_bound = int(raw_input("Ending question number: ")) + 1

delimited = date.strip().split("-")
date_str = ""
for v in delimited:
	if len(v) < 2:
		date_str += "0"
	date_str += v

web_page = requests.get("http://www.kanoon.ir/Public/TestKey.aspx?td=%s&gc=1"%date_str)
p = re.compile(ur'id="CPCM_CPC_hdDate"[^\/]+value="([^"]+)"')
try:
	token = re.search(p, web_page.content).groups()[0]
except:
	print "A problem!"
	exit(0)
	

#lower_bound = 238
#upper_bound = 250


for i in range(lower_bound, upper_bound +1):
	sys.stdout.write("Downloading question #%s" % i + '\r')
	link = "http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/%s/Question/%s&dd=%s" %(class_code, i, token)
	response = requests.get(link, stream=True)
	if int(response.headers.get('content-length')) < 2 * 1024: # if file is too small (empty)
		if i == lower_bound:
			print "Problem with lower bound"
			exit()
		upper_bound = i
		break
	if not os.path.exists(date_str + "\\" + "Questions"):
		os.makedirs(date_str + "\\" + "Questions")
	with open(date_str + "\\" + "Questions" + "\\" + '%s.png'%(i), 'wb') as out_file:
		shutil.copyfileobj(response.raw, out_file)
	del response
print "Downloaded " + str(upper_bound-lower_bound) + " questions successfully!"

for i in range(lower_bound, upper_bound+1):
	sys.stdout.write("Downloading answer #%s" % i + '\r')
	link = "http://www.kanoon.ir/Common/Handler/AzmoonTest.ashx?pic=/1/Answer/%s&dd=%s" %(i, token)
	response = requests.get(link, stream=True)
	if not os.path.exists(date_str + "\\" + "Answers"):
		os.makedirs(date_str + "\\" + "Answers")
	with open(date_str + "\\" + "Answers" + "\\" + '%s.png'%(i), 'wb') as out_file:
		shutil.copyfileobj(response.raw, out_file)
	del response
print "Downloaded " + str(upper_bound-lower_bound) + " answers successfully!"
کدِ من به شکلِ بالا شد؛ شما قطعاً می‌توانید برنامه را به شکلِ بهتری اجرا کنید، خوش‌حال می‌شوم برنامه‌ی شما را نیز ببینم.
* برای اجرای پروژه از کتابخانه‌ی عالیِ requests در پایتون استفاده کرده‌ام که برای استفاده لازم است آن‌را با استفاده از pip نصب کنید.

با کمی تغییرات و دسته‌بندیسم، خروجیِ برنامه‌ام این‌گونه شد:
خروجیِ کار

برنامه هم این‌طور کار می‌کند:
خروجیِ برنامه‌ی پایتون

تولدت مبارک ابراهیم... 3>