不管是C#還是Java,都是可以通過反編譯工具,進行反編譯后查看源碼,這個源碼雖然不是真正意義的源碼,但和真正源碼的相差不是很大.如果是單純的Web還好一些.因為只需要部署到服務器上,也很少人能看到部署文件.所以相對要好一些.如果是C#做客戶端的話,是需要安裝到客戶機上的.所以還是需要對程序做一下處理,對程序代碼進行代碼混淆.
這里主要使用Obfuscator這個工具,Obfuscator是源碼開源.且也是國人Lex Li(已經(jīng)出國)所寫.
1.在Nuget中,搜索Obfuscator,并進行安裝
在Nuget瀏覽器中,搜索Obfuscator
2.創(chuàng)建obfuscar.xml,并設置該文件為較新復制
在屬性中,設置文件較新復制
3. 在obfuscar.xml中指定配置信息
<?xml version="1.0" encoding="utf-8" ?>
<Obfuscator>
<!--輸入路徑-->
<Var name="InPath" value="." />
<!--輸出路徑:加密混淆過的路徑-->
<Var name="OutPath" value=".\Obfuscator_Output" />
<!--混淆代碼的參數(shù)-->
<Var name="ReuseNames" value="false" />
<Var name="HideStrings" value="false" />
<Var name="KeepPublicApi" value="false" />
<Var name="HidePrivateApi" value="true" />
<!--要混淆的模塊-->
<Module file="$(InPath)\ConsoleApp2.exe" />
</Obfuscator>
參數(shù)信息,可以看這里: https://docs.obfuscar.com/getting-started/configuration.html
4.在程序生成之后調(diào)用Obfuscar.通過VS的生成事件實現(xiàn).
在VS中生成后事件,調(diào)用Obfusacr 并加載obfuscar.xml
5.在Obfuscator_Output目錄,通過反編譯工具JustDecompilte查看混淆過的程序.
使用反編譯工具JustDecompilte查看混淆過的代碼
Obfuscator可以不在VS中直接使用,Obfuscator是一個單純的代碼混淆工具. 上邊說過Obfuscator代碼是開源的.
在GitHub項目地址: git@github.com:obfuscar/obfuscar.git
在Gitee項目地址: https://gitee.com/junweihuang_admin/obfuscar.git
因為Gitee速度要快于GitHub,如果Gitee上有的話,還是優(yōu)先使用Gitee. 如果Gitee上面沒有的話,就得去Github上.打開Obfuscar.sln解決方案.進行編譯.發(fā)現(xiàn)是無法編譯成功的.發(fā)現(xiàn)Obfuscator依賴Baml項目中.
在Baml項目中,發(fā)現(xiàn)這幾個不存在的.
發(fā)現(xiàn)Baml項目中,有4個文件不存在
在Obfuscator項目中的Obfuscator.cs發(fā)現(xiàn)ILSpy.BamlDecompiler.Baml命名:
發(fā)現(xiàn)了ILSpy.BamlDecompiler.Baml這個命名空間
便懷疑是不是缺的這幾個文件在IlSpy中呀!于是便把IlSpy源碼下載到本地,最終找到了這幾個文件.將這幾個文件拷貝到Baml項目中,嘗試編譯,果真是編譯成功了.
生成后的目錄:
Obfuscar代碼混淆工具,只需要將要混淆的程序放入Input路徑下,在obfuscar.xml修改指定,雙擊run.bat就可以了
Obfuscar.Console.exe使用:
文分享的是一個動畫效果源碼,演示JS代碼是如何被加密的。
先看效果:
一般我們在進行JS加密時,提交原始代碼,緊接著就直接得到了加密代碼,混淆加密過程是黑盒狀態(tài),是不被我們知道的。
這個動畫,用慢放的效果,逐幀演示了JS代碼在進行混淆加密時發(fā)生的變化。
比如:變量名變短、回車換行消失、空格刪除、消除注釋、函數(shù)調(diào)用變成自執(zhí)行函數(shù)。
注:動畫演示的是JShaman專業(yè)版使用部分功能對JS代碼進行混淆加密的效果。
下面給出這個動畫完整源碼,保存為html文件即可使用:
<h2>動畫演示:JShaman是怎樣對JS代碼混淆加密的</h2>
<button onclick="mini_change();">開始</button><br>
<br>
<textarea id="js_code" style="width:800px; height:200px;font-size:19px;">
function get_copyright(){
var domain="JShaman專注于JS代碼混淆加密";
var from_year=2017;
var the_copyright="(c)" + from_year + "-" + (new Date).getFullYear() + "," + domain;
return the_copyright;
}
//輸出信息
console.log(get_copyright());
</textarea>
<br>
注:演示的是JShaman專業(yè)版部分效果
<script>
var index=0;
var js_code_textarea=document.getElementById("js_code");
var change_matrix=[
["\n",""],
[" ",""],
["var domain","var _"],
["\n",""],
[" ",""],
["var from_year","var _2"],
["\n",""],
[" ",""],
["var the_copyright","var _3"],
["from_year","_2"],
["\n",""],
[" ",""],
["\n",""],
["the_copyright","_3"],
["domain","_"],
["\n",""],
["//輸出信息",""],
["\n",""],
["function get_copyright(){",""],
["}",""],
["get_copyright()","function(){"+`var _="JShaman專注于JS代碼混淆加密";var _2=2017;var _3="(c)" + _2 + "-" + (new Date).getFullYear() + "," + _;return _3;`+"}()"],
[`var _="JShaman專注于JS代碼混淆加密";var _2=2017;var _3="(c)" + _2 + "-" + (new Date).getFullYear() + "," + _;return _3;`,""],
]
function mini_change(){
js_code_textarea.value=js_code_textarea.value.replace(change_matrix[index][0], change_matrix[index][1]);
index++;
console.log(index[0]);
if(index >change_matrix.length-1){
console.log("執(zhí)行");
eval(js_code_textarea.value);
alert("演示完成");
return;
}else{
setTimeout(mini_change,1000);
}
}
</script>
這段代碼中,應用了一些不錯的JS技巧,參考修改,也可以用來做其它文本演示動畫。
們已經(jīng)學到很多反爬機制以及相應的反反爬策略。使用那些手段,其實已經(jīng)完全可以完成絕大多數(shù)的爬蟲任務。但是,還是有極個別的情況下,會出現(xiàn)諸如 JS 加密和 JS 混淆之類的高深反爬機制。
如果不幸遇到這種反爬機制,一個明智之舉是給站長點個贊,然后恭恭敬敬選擇放棄,去別的地方找數(shù)據(jù)。
當然,還是那句話,我們可以選擇不爬,但是對付 JS 加密和 JS 混淆的方法卻不可以不會。
這里就以中國空氣質(zhì)量在線檢測平臺為例,介紹 JS 加密和 JS 混淆的實現(xiàn)和破解方法。
要爬取的網(wǎng)站:https://www.aqistudy.cn/html/city_detail.html
這個網(wǎng)站正在升級,所以頁面無法正常顯示。這也意味著這個網(wǎng)站本身的 JS 解密是有問題的(如果沒問題就能顯示了),所以最后我們并不能完全解析出數(shù)據(jù)來。雖然如此,這個網(wǎng)站仍然是學習 JS 加密和 JS 混淆的相當不錯的平臺。
閑話少說,開始干活!
首先瀏覽器打開網(wǎng)頁,并打開調(diào)試臺的抓包工具。修改查詢條件(城市的名稱 + 時間范圍),然后點擊查詢按鈕,捕獲點擊按鈕后發(fā)起請求對應的數(shù)據(jù)包。點擊查詢按鈕后,并沒有刷新頁面,顯然發(fā)起的是 ajax 請求。該請求就會將指定查詢條件對應的數(shù)據(jù)加載到當前頁面中(我們要爬取的數(shù)據(jù)就是該 ajax 請求請求到的數(shù)據(jù))。
分析捕獲到的數(shù)據(jù)包
該數(shù)據(jù)包請求到的是密文數(shù)據(jù),為何在前臺頁面顯示的卻是原文數(shù)據(jù)呢?
原來,在請求請求到密文數(shù)據(jù)后,前臺接受到密文數(shù)據(jù)后使用指定的解密操作(JS 函數(shù))對密文數(shù)據(jù)進行了解密操作,然后將原文數(shù)據(jù)顯示在了前臺頁面。
接下來的工作流程:
首先先處理動態(tài)變化的請求參數(shù),動態(tài)獲取該參數(shù)的話,就可以攜帶該參數(shù)進行請求發(fā)送,將請求到的密文數(shù)據(jù)捕獲到。
抽絲剝繭,首先從 getData 函數(shù)實現(xiàn)中找尋 ajax 請求對應的代碼。在該函數(shù)的實現(xiàn)中沒有找到 ajax 代碼,但是發(fā)現(xiàn)了另外兩個函數(shù)的調(diào)用,getAQIData() 和 getWeatherData()。ajax 代碼一定是存在于這兩個函數(shù)實現(xiàn)內(nèi)部。
另外,這里記住一個參數(shù),type==’HOUR‘,它的含義是查詢時間是以小時為單位。這個參數(shù)我們后來會用到。
接下來我們就去分析 getAQIData() 和 getWeatherData(),爭取能夠找到 ajax 代碼。
我們找到這兩個函數(shù)的定義位置,還是沒有找到 ajax 請求代碼。不過我們卻發(fā)現(xiàn)它們同時調(diào)用了另外一個函數(shù),getServerData(method,param,func,0.5)。它的參數(shù)的值可以為:
下一步當然就要找 getServerData 函數(shù)了,看看那個函數(shù)里面有沒有我們一致想要的發(fā)送 ajax 請求的代碼。
我們嘗試著在頁面中搜索,卻找不到這個函數(shù)。很顯然,它是被封裝到其他 js 文件中了。這時,我們可以基于抓包工具做全局搜索。
好消息是,我們順利找到了 getServerData 函數(shù)!壞消息是,這貨長得一點也不像是函數(shù)。
這是因為,這段 JS 函數(shù)代碼被加密的。這種加密的方式,我們稱為 JS 混淆。
JS 混淆,也就是對核心的 JS 代碼進行加密。
JS 反混淆,則是對 JS 加密代碼進行解密。
接下來我們要做的,就是 JS 反混淆,讓這段我們看不懂的東西,顯現(xiàn)出廬山真面目。
我們用的方法十分簡單粗暴,也就是暴力破解。使用這個網(wǎng)站就可以實現(xiàn)對 JS 混淆的暴力破解:https://www.bm8.com.cn/jsConfusion/
將 getServerData 函數(shù)所在的那一整行代碼都復制過來,粘貼到這個網(wǎng)址的文本輸入框中,然后點擊 開始格式化 即可:
終于,我們看到了 getServerData 的代碼,并且在其中發(fā)現(xiàn)了發(fā)送 ajax 的請求:
function getServerData(method, object, callback, period) {
const key=hex_md5(method + JSON.stringify(object));
const data=getDataFromLocalStorage(key, period);
if (!data) {
var param=getParam(method, object);
$.ajax({
url: '../apinew/aqistudyapi.php',
data: {
d: param
},
type: "post",
success: function (data) {
data=decodeData(data);
obj=JSON.parse(data);
if (obj.success) {
if (period > 0) {
obj.result.time=new Date().getTime();
localStorageUtil.save(key, obj.result)
}
callback(obj.result)
} else {
console.log(obj.errcode, obj.errmsg)
}
}
})
} else {
callback(data)
}
}
從這段代碼中,我們不難得出下面這幾個信息:
但是我們并不打算這么做。因為再繼續(xù)深挖下去,難度將會陡然增加。此時我們已經(jīng)很疲憊了,如果繼續(xù)下去恐怕要瘋掉。而且,JavaScript 和 Python 畢竟是兩種語言,它們之間的方法和各種包都不相同。JavaScript 能實現(xiàn)的,Python 未必能夠輕松完成。所以重新寫一個加密和解密的腳本,并不是明智之舉。
更好的解決方案是,我們提供請求的明文數(shù)據(jù),通過網(wǎng)站自己的 JS 代碼進行加密,得到加密的請求參數(shù)。使用這個參數(shù),我們發(fā)送請求給服務端。拿到加密的響應數(shù)據(jù)后,再通過網(wǎng)站的 JS 代碼進行解密。
也就是說,我們接下來需要做的就是要調(diào)用兩個 JS 函數(shù) decodeData 和 getParam,并拿到返回結果即可。
現(xiàn)在的問題是,在 Python 程序中如何調(diào)用 JS 函數(shù)呢?
這就涉及到一個新的概念:JS 逆向。JS 逆向,也就是在 Python 中調(diào)用 JS 函數(shù)代碼。
能夠?qū)崿F(xiàn) JS 逆向的方式有兩種:
pip install PyExecJS
接下來,我們就可以生成加密的請求數(shù)據(jù)了。
首先,把我們解析出來的那串代碼保存到本地,比如名為 code.js 的文件中。在里面我們補充一個函數(shù),比如名字叫 getPostParamCode,用來發(fā)起我們的數(shù)據(jù)請求。之所以這樣做是因為使用 PyExecJS 調(diào)用 JS 函數(shù)時,傳入的參數(shù)只能是字符串。而 getParam 方法的參數(shù)需要用到 JS 的自定義對象。
我們只需在 code.js 中加上下面的代碼即可:
function getPostParamCode(method, type, city, start_time, end_time) {
var param={};
param.type=type;
param.city=city;
param.start_time=start_time;
param.end_time=end_time;
return getParam(method, param)
}
然后,使用 PyExecJS 調(diào)用里面的 getParam 方法,將我們的請求數(shù)據(jù)加密:
# 模擬執(zhí)行decodeData的js函數(shù)對加密響應數(shù)據(jù)進行解密
import execjs
import requests
node=execjs.get()
# 請求參數(shù)
method='GETCITYWEATHER'
type='HOUR'
city='北京'
start_time='2020-03-20 00:00:00'
end_time='2020-03-25 00:00:00'
# 編譯js代碼
file='code.js' # js代碼的路徑
ctx=node.compile(open(file, encoding='utf-8').read())
# 將請求數(shù)據(jù)加密
encode_js=f'getPostParamCode("{method}", "{type}", "{city}", "{start_time}", "{end_time}")'
params=ctx.eval(encode_js)
# 使用加密的參數(shù),發(fā)起post請求
url='https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text=requests.post(url, data={'d': params}).text
# 將響應數(shù)據(jù)解密
decode_js=f'decodeData("{response_text}")'
decrypted_data=ctx.eval(decode_js) # 如果順利,返回的將是解密后的原文數(shù)據(jù)
print(decrypted_data) # 執(zhí)行會報錯:目前頁面中沒有數(shù)據(jù)。解密函數(shù)只是針對頁面中原始的數(shù)據(jù)進行解密。
自此,我們完成了 JS 加密和 JS 混淆的處理。這里我們總結一下這幾個概念:
附,ajax 請求的各個數(shù)據(jù)的含義:
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。