最近學院出了個答題小程序,筆者簡單測試了一下發現很適合練手,於是簡單準備準備就踏上了破解之路。
我文中的「破解」,和信息安全中的「破解」有所區別,我所說的破解,是用非正常的途徑去達到自動答題的效果,如有信息安全大佬,請輕噴。文中所寫只作於學習和交流使用,請勿用作違法犯罪等目的。(歡迎各位有想法的小夥伴後臺留言指教)
筆者也是新手,很多東西都是現查現用,在完成整個程序之後回頭想想,如果在寫代碼之前再多一些思考,很多坑就可以直接跳過,程序也不會一開始就很低的效率,寫的代碼可能也會更優雅,更穩定。
本文主要記錄筆者在破解程序的過程中走的一些路踩過的一些坑,其中包含:對不了解的技術查資料的過程,在已完成功能的基礎上優化的過程,踩坑的過程和從坑裡面跳出來的過程,解決BUG的苦逼過程等等等等。
如果讀者你和我一樣是個新手,我希望你能從文中獲取下面這些東西:
如果讀者你已經是個老司機了,我希望你能從我的文章中了解到一些技術/知識的思路,一點點就好,一點點我就會很開心的。
程序介紹只介紹答題部分,進入程序會有一個這樣的界面:
點擊開始答題之後:
正常題目:
答對之後對應選項會變綠然後直接進入下一題,如果答錯,會有下面這樣的圖:
答完之後就會:
暴力方法最開始上手的時候,那時的我對安卓的了解就僅限於日常使用,破解一個安卓程序我是沒想過,但是我想起來我以前用安卓模擬器玩過遊戲,那安卓模擬器沒準可行,安卓不熟悉,但是PC端我熟啊,打開一個模擬器,然後之後只要對這個模擬器進行不同的操作,就可以達到破解的效果了。
然後開始百度,隨便搜了個安卓模擬器——逍遙模擬器,用模擬器安裝一個微信後,登錄帳號,找到小程序,進入,讀條,就在一切都順風順水的時候,模擬器就在我的美好期待中回到了微信主界面,我以為我看錯了,又點了一遍,又讀條,又跳回了主界面。此時我的心情是:
出現問題,就得解決問題,我先用手機重新登錄了一下微信,進入小程序,完美進入。說明小程序是沒有問題的,那會有什麼因素呢?我們簡單列舉一下:
列了個表,那就一個一個來吧。手機因素最先排除,我找到一個安卓手機發現可以完美的進入小程序。配置因素,思考了一下,純屬扯淡……整個小程序唯一的圖片就是我的頭像,哪存在什麼配置因素。網絡因素,通過模擬器測試了一下正常操作,微信聊天、瀏覽器瀏覽網頁,都沒有任何問題,網絡因素也不存在。模擬器因素,我開始嘗試找不同的模擬器,就把市場上流行的各種模擬器下了個遍——什麼夜神模擬器、MuMu模擬器、VisualStudio的模擬器,下了五六個吧,統一不行,也排除了。
那就考慮微信因素了,微信的話市場應用,大bug應該不會有,是不是版本問題?然後我開始找微信的低版本,看了一下最新版本是6.7.3(安卓),那就找個低的試試吧,從微信官網找到更新記錄,試圖下一個低版本的微信,然後就被坑了。微信官方更新記錄,無論是多低版本的文章,下載連結都引用的是最新版本的下載連結,。官方不給下,那就得找第三方, 這時就應該吐槽一下國內軟體市場之垃圾,下載連結經常和介紹不符,很多直接引的最新版本,經過一番挑選和折磨,找到了幾個版本,經過測試,在模擬器中,微信6.7.3版本是進不去小程序的,6.7.3版本以下都會提示我微信版本過低,請更新微信。最終可以發現,某些低版本的微信可以通過模擬器進入部分小程序——抽獎助手和一些第三方小程序等等,但是沒有辦法進入我們想進的小程序,總結一下可以想到:是微信官方禁止了模擬器進入新版本的小程序,可能是要防止這方面的作弊。那找到因素了,就是微信因素。
思考:我們可以想一下官方如何識別是模擬器還是手機,可能是在微信應用在請求小程序的時候加了一些驗證信息,這些驗證信息和手機本身有關,模擬器發請求的話可能會在這些驗證信息上露出馬腳,民間研究模擬器的團隊大都著眼於提高模擬器的性能,提高渲染質量,降低內存佔用這些性能上的指標,可能不會注意到這樣的細枝末節,可以理解。至於模擬器在發請求的時候如何漏出馬腳,筆者至今沒有找到具體的點,希望讀者在找到之後可以交流一下。
知道問題所在了,那就想辦法解決吧。一般來說,遇到問題了有兩個途徑去解決問題——一是正面剛問題,用技術的方法去解決問題,非常硬核,二是繞過問題,使用一些巧妙地辦法讓我們不遇見這個問題。
先說一吧,正面剛問題,找到模擬器為什麼進不去小程序的原因,然後想辦法偽造請求,讓微信認為你這個模擬器是手機,我想了一下,我能力不夠,pass。能力不夠,但是我可以問人啊,然後我就到逍遙安卓模擬器的論壇發了個帖子。最後到現在都沒人理我……
那就想法繞過問題吧,咋繞過?用一臺真正的手機做題不就好了。可我手上只有一個iPhone7,iPhone一個閉源系統,權限控制是出了名的嚴格,最開始我就沒打算直接控制iPhone,那就弄安卓吧,沒安卓,買一個。打開淘寶,搜索Nexus 5X,下單付款,一氣呵成。下單是下單了,手機得有幾天才能到呢,這幾天不做心痒痒啊,借吧。
借了一圈借了2個手機,1個魅族,1個vivo,我直接拿起魅族上手了。按照最開始的思路,我操縱安卓並不熟悉,先百度了一通如何用PC操縱手機,大概有兩個辦法,一個就是用投屏軟體,就是在PC的屏幕上投射出來一個手機的屏幕,在這個屏幕上點一下就等於點擊了手機屏幕,以此達到控制安卓的效果。二就是用谷歌官方給的adb套件,這個是什麼我們一會兒再說。
先說投屏,就是用一個軟體來在PC屏幕上顯示手機的屏幕,百度了一通軟體,什麼vysor,scrcpy,Airdroid之類的,大概五六款吧,除了查這些軟體,還要針對每一個軟體查使用流程,有些軟體還要翻牆使用谷歌帳號登陸才能使用巴拉巴拉的,費了大概半天時間(是真的半天,六七個小時),最後怎麼也沒辦法將魅族的屏投到電腦上。
這個時候人就很容易自閉,六七個小時發現這個東西解決不了。人一自閉眼眉就容易低垂,人的眼眉一低垂,就容易看到桌子上一直被冷落的vivo(心中:「這個小機器是不是可以!」)掏出來數據線就插上了vivo,打開vysor,等待連接,連接成功,投屏成功。桌面上出現了一個美麗的界面,就是我心心念念的安卓界面。滑鼠點擊兩下,手機也接受到了點擊,我成功了。
人在快要自閉的邊緣被生活這麼搞一下,就特別容易膨脹,有句老話說得好:恐懼到的極點就是憤怒,自閉到了極點就是膨脹。
這個時候我就不怎麼思考了,打開IDE就開始按照我之前的思路寫代碼,我之前想的答題思路沒有寫出來,現在用一個圖來表示:
那就從第一步開始吧,獲取題目文字,咋獲取?這是小程序,不像網頁,不能讓我右鍵點點查看原始碼就看到文字了。分析了一下,似乎是一張圖片(後來發現不是圖片,這個後文再說),不會弄,那就搜吧,搜了一些關於圖片轉文字的東西,查到了tesseract-OCR工具,主要是因為它是個開源工具同時用過它的人比較多,這意味著如果你用這個工具出現了什麼問題,很容易從其他人的經驗那裡找到問題所在。
用Python測試了一下這個庫,隨便用了幾個標準的英文圖片來測試,還挺準的。又去學了一下Python的PIL庫(一個圖像處理庫),寫了個截圖的函數,截取當前屏幕上的指定位置,這個時候就遇到問題了,截取哪裡?剛開始我就框定了模擬器中題目那一個方塊,就截取這一部分,但是之後我不小心移動了模擬器,那麼我就得重新對準題目那一塊了,就是要改代碼,將截圖的區域改到新的區域,這怎麼能行……對於一個程式設計師來說改代碼是最不能忍受的。那就想辦法解決這個問題。
絕對坐標不行的話,就要用相對坐標了,之前是給定一個確定的值,就截圖這一塊,不管其他的,現在不行,現在要根據投屏窗口在的不同位置,截圖不同的位置,想想也簡單,獲取到窗口的左上角坐標的x值和y值作為標準值,之後只需要確定題目區域離這個標準值偏移多少就可以了。如下圖:
截圖問題解決了,那就進行題目的提取吧,使用tesseract-OCR來處理圖片,識別失敗。重新識別,又失敗。之後繼續查資料,發現不配置的情況下tesseract-OCR默認只能識別英文圖片,要自己選擇識別中文,它才能識別中文,然後發現識別中文需要中文模型庫……又官網折騰了一通下載下來安裝上了,這下成功了。題目上的文字成功的轉成了文本。
轉是能轉……可是準確率沒那麼高,有的時候像「下面說法正確的是」這樣的圖片可能識別出來的就是「下而說法正確的是」,之後開始想辦法提高準確率(其實這就開始走錯路了),查了一大堆圖像處理的東西,什麼圖像二值化、圖像去噪、圖像濾波很多東西,對著圖像折騰了半天也沒提高多少準確率……這個時候我開始反過頭來思考,想想自己是不是做錯了。我為什麼要提高準確率?就算第一遍識別出的不對,那麼第二遍識別出來仍然是這個結果,仍然能對應到題庫中,不會影響我的程序啊,這個時候就已經過去好幾個小時了,圖像處理以前沒有搞過,查資料就要一邊查一邊學一邊寫代碼實驗,很痛苦,不過還好最後不用做了。
題目獲取到了,即便文本不對,不影響程序的正常運行。那就開始考慮答案的獲取吧,怎麼記錄一個題目對應的答案呢。最先想到的記錄ABCD,然後ABCD對應不同的坐標,點擊對應坐標就好了,測試了一下發現不行……題目/選項長度不同,ABCD的位置也是不同的,那就不能記錄ABCD了,於是最暴力的地方來了,直接記錄坐標是可行的,我不用管點的是A還是B還是C,我只需要碰到這個題點這個坐標,確定這個坐標對應的是正確答案就可以了。
那就記錄吧……比如說這個題目的答案對應的是(94,335)這個坐標,那就記錄下來,下次遇到這個題就點屏幕上的這個坐標。然後每次遇到一個新題我就記錄一個坐標,非常可行。就這麼記了幾十道題……我突然發現ABCD的橫坐標是相同的,就是他們距離屏幕左側有一樣的距離(x值相同),那麼每次我只需要測量一個y值就可以了,於是改了一下代碼,工作量減半。我們現在的代碼偽代碼是這樣的:
while 1: if 是開始界面: 點擊開始答題 else if 是結束界面: 點擊結束答題 else: 截取題目圖片 通過OCR獲取題目文本 在題庫中搜索題目文本 if 存在: 點擊對應坐標 else: 輸入坐標 存入該題
這個方法暴力可行,最終錄完的所有題庫,這個代碼就是能跑起來的了,能達到自動答題的效果。可是經過檢測,答題速度很慢,而且代碼很不穩定,經常有誤操作,比如點錯了選項,在一些加載頁面不知道怎麼處理導致卡頓。
誤操作問題經過檢測,發現是點擊投屏上的頁面傳到安卓手機上,再從安卓手機上傳回電腦這個過程有延遲,有可能點完之後投屏還沒反應過來,已經進行了下一次截圖,然後識別,這個時候投屏已經反應過來了,而程序卻識別成了上一道題,進而點錯了選項。而且有的時候結束答題、開始答題什麼的都會有一個短的加載頁面,可是截圖很容易就截到這部分,不穩定,不行。
先解決加載頁面問題,這個好解決,每次點擊完開始答題或者結束答題的時候,使用Python的庫來讓程序暫停一段時間不運行,也就是time.sleep函數,簡單測量一下加載頁面會有多長時間,等待這麼多時間再運行代碼,就不會讓截圖截到加載頁面。
我們給程序加了暫停機制……就更慢了。要想辦法優化效率,經過一番目測加上代碼的時間測試,OCR這步超級慢,截圖0.2s~0.4s,OCR要1.5s~2s,點擊耗時忽略不計,安卓設備和PC端的畫面同步時間沒辦法精確測量,就沒測。
有沒有辦法去掉OCR呢?有的,我們假定,一個題目的圖片必定會對應一串題目文本,一串題目文本必定會對應一個答案坐標,前後都是一對一的關係,那麼我們去掉文本這個東西,一個題目的圖片必定會對應一個答案坐標,沒問題吧?沒問題。那還要個P的OCR。更改一下代碼邏輯,去掉OCR,完美運行,速度大幅提升。
速度是快了,穩定性又降低了, 代碼運行速度的增快,會導致截圖截到上一道題的可能性更大,遇到的錯誤也就更多,需要人工幹預的地方也就更多,就不能稱之為自動答題。
解決方案也很簡單,每答完一道題等待一段時間,這段時間只要比PC和安卓之間通信的時間長,那就可以解決這個問題。除了這個,我們還有另外一種方法去解決問題,那就是維護一個變量,這個變量叫做「上一道題」,如果這次做的題等於「上一道題」,就重新識別,這樣就不會有了截圖到上一道題的可能性了,只要是上一道題就重新執行。
最終的偽代碼是這樣的:
while 1: if 是開始界面: 點擊開始答題 else if 是結束界面: 點擊結束答題 else: 截取題目圖片 在題庫中搜索題目圖片 if 存在: if 是上一道題: continue 點擊對應坐標 上一道題 = 這一道題 else: 輸入坐標 存入該題 上一道題 = 這一道題
這個代碼運行的還是挺好的。可以持續的刷題了。可是代碼中仍然有一個問題的點,就是這個存入該題這一行,如果碰到了一個新題,而我持續沒有輸入,那這個程序就會一直堵塞到這個地方,也就刷不了題了。我也不能一直盯著它做題啊,那還叫個自動嗎?於是又開始查資料,查了一些並發編程的知識、計時器的知識、以及程序如何重啟本身的知識。
由於題目是隨機的,那遇到一個新題,只要我不在電腦前面,一段時間未輸入之後,程序自動重啟,並重新開始答題,總不能還隨機到這個題吧,再碰到不會的,我沒輸入,繼續重啟,直到可以做了。這樣會降低性能和做題速度,但是穩定性大大提升。於是就在代碼中新開了個線程作為計時器,計時器會自動計算時間,只要計算時間超過一個值了(這個值我們自己設定),那程序就自動重啟。而答題呢,每次答題成功了,都刷新計時器,讓它重新計時,這樣就達到了一段時間不輸入就自動重啟的效果。
至此這個程序已經可以自己答題了,它能自動搜集答案,能遇到不會的自動重啟,能跳過一些無用的加載頁面,你就可以開著這個程序回去睡覺了。表面看無大礙了,實則有大坑。
我們現在在幹嘛?在把安卓的界面投影到電腦上,我們所有的代碼都是以這個為基礎做的,換一種說法,我們的代碼正在依賴這個投影,這個投影只要有一點問題,比如投影的不清晰,解析度的縮放不對,我們的代碼可能就失效了,可能整個題庫都會因此失效,我們要消除這種依賴。更煩的是,我們現在操控的是滑鼠,如果你要運行這個代碼,那麼你就不能幹其他的事,否則代碼失效,必須解決這個問題。
思考:有可能讓代碼不依賴滑鼠,筆者最先去查了一下python和windows交互的一些api,又查了一些作業系統的原理,覺得滑鼠點擊一個坐標的原理可以看做是幾個動作的集合:找到這個坐標點對應的最頂層的窗口,找到這個窗口對應的進程,向這個進程這個坐標點發送一個滑鼠點擊事件,最終查閱了很多資料也沒有成功在後臺模擬滑鼠點擊。後來我想到我小學的時候使用一些按鍵精靈的腳本,它可以讓一個遊戲窗口後臺運行並後臺模擬點擊,不過當時沒有時間去研究這些軟體的原理,也就放棄了,希望讀者有思路的話可以多多交流。
雖然代碼不夠優雅,不過通過本篇文章還是可以實現一個簡易的demo,放一張效果圖吧:
本文作者:王悟空