Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
初在研究對(duì)移動(dòng)網(wǎng)絡(luò)傳輸進(jìn)行功耗優(yōu)化,在一次意外的監(jiān)聽網(wǎng)絡(luò)傳輸包中截獲了微信小程序的請(qǐng)求包,借此來窺探當(dāng)下前端代碼安全。
0x01 小程序分析
小程序包結(jié)構(gòu)
Segment | Name | Length | Remark |
---|---|---|---|
Header | FirstMark | 1 byte | 0xBE 固定值 |
Edition | 4 bytes | 0 -> 微信分發(fā)到客戶端 1 -> 開發(fā)者上傳到微信后臺(tái) | |
IndexInfoLength | 4 bytes | 索引段的長(zhǎng)度 | |
BodyInfoLength | 4 bytes | 數(shù)據(jù)段的長(zhǎng)度 | |
LastMark | 1 byte | 0xED 固定值 | |
FileCount | 4 bytes | 文件數(shù)量 | |
Index | NameLength | 4 bytes | 文件名長(zhǎng)度 |
Name | NameLength bytes | 文件名,長(zhǎng)度為NameLength | |
FileOffset | 4 bytes | 文件在數(shù)據(jù)段位置 | |
FileSize | 4 bytes | 文件大小 | |
LOOP...... | |||
Data | Files Package...... |
包結(jié)構(gòu)非常清晰,分為三個(gè)部分:
頭信息,包含一些包的標(biāo)識(shí),版本定義等,包含了三個(gè)冗余字段 --- 索引段和數(shù)據(jù)段的長(zhǎng)度應(yīng)該是用于做校驗(yàn),但實(shí)質(zhì)上沒有用(設(shè)計(jì)者覺得需要設(shè)計(jì)一些冗余字段來確保設(shè)計(jì)的完整性,防止解析的時(shí)候溢出,但實(shí)際工程實(shí)踐中并沒有起到相應(yīng)的作用),文件數(shù)量應(yīng)該是用于簡(jiǎn)化包解析過程,實(shí)際上知道了索引段長(zhǎng)度或數(shù)據(jù)段長(zhǎng)度中任何一個(gè)皆可推算出文件數(shù)量。
索引段,包含文件的元信息 --- 文件名以及文件位置(通過FileOffset和FileSize定位數(shù)據(jù)段中的文件)。如果從精簡(jiǎn)包的大小的角度來看,F(xiàn)ileOffset和FileSize只需一個(gè)存在即可,但是這樣解析包的難度就大大增加了,還是以工程實(shí)踐為主。
數(shù)據(jù)段,將所有的文件羅列在一起。
由此可見,數(shù)據(jù)完全沒有經(jīng)過壓縮或者加密,連包的簽名信息也沒有。這導(dǎo)致只能在制品流程上進(jìn)行嚴(yán)格控制,例如在開發(fā)者上傳代碼過程中需要授信,必須經(jīng)過審查,也一定得由授信平臺(tái)進(jìn)行代碼分發(fā)等。這些都無關(guān)風(fēng)月,畢竟App Store就是這種模式,但是......
如何拆解這種自定義文件格式呢?
對(duì)多個(gè)相同格式的文件進(jìn)行對(duì)比,對(duì)大體結(jié)構(gòu)有宏觀的感覺,很容易發(fā)現(xiàn)一些固定的字段以及一些結(jié)構(gòu)的長(zhǎng)度。對(duì)于像小程序這種有軟件本體的例子,還可以通過微量修改來觀察文件的變化來找到文件結(jié)構(gòu)和意義。
觀察特殊形式,首先英文的字符串是很明顯的,一般hex編輯器都自帶字符串化窗口,如果發(fā)現(xiàn)常見的字符串,就可以繼續(xù)去尋找字符串的邊界,字符串在二進(jìn)制文件里有兩種儲(chǔ)存方式:一種是不記錄字符串的長(zhǎng)度,讀取字符串到0x00位置,另一種一定在某一個(gè)地方儲(chǔ)存了這個(gè)字符串的長(zhǎng)度,因此一旦得知了該字符串的內(nèi)容,搜索該長(zhǎng)度字段即可獲取更多的信息。其次一些文件頭也非常顯眼,例如PNG[1]、ZIP[2]等通用標(biāo)準(zhǔn)文件格式都有固定的文件頭,在小程序的自定義格式中很容易發(fā)現(xiàn)一些png、jpg等資源的文件頭,因此可以定位數(shù)據(jù)區(qū)的位置。
對(duì)特定區(qū)域的二進(jìn)制進(jìn)行推理猜測(cè),一般來說二進(jìn)制文件里需要儲(chǔ)存大量的offset和size的數(shù)據(jù)作為數(shù)據(jù)段的索引。offset相當(dāng)于一個(gè)指針 - 索引文件在數(shù)據(jù)段的位置,工程實(shí)踐中,大部分儲(chǔ)存了offset的地方也會(huì)儲(chǔ)存size字段,畢竟在解析文件的時(shí)候會(huì)方便很多,也可以防止校驗(yàn)數(shù)據(jù)出現(xiàn)指針越界。因此,一旦確認(rèn)了文件中的數(shù)據(jù)段,就可以通過它的位置(offset)和大小(size)的實(shí)際數(shù)據(jù)進(jìn)行搜索,逆向找到指向它的數(shù)據(jù)位置,并且繼續(xù)逆向直到解析完整的文件。另外,如果要考慮設(shè)計(jì)的完備性,需要在二進(jìn)制文件中加入一些冗余字段進(jìn)行校驗(yàn)或者糾錯(cuò),例如CheckSum、CRC32、Alder32、MD5、ECC等,這些通過hex編輯器很容易計(jì)算并發(fā)現(xiàn)。小程序中FileCount的字段,這完全是為了工程實(shí)踐考慮的,在小程序中并沒有出現(xiàn)這類的計(jì)算值,這是可能是因?yàn)樾〕绦驗(yàn)榱撕?jiǎn)單設(shè)計(jì)考慮,一旦發(fā)現(xiàn)包體被篡改或損壞就直接丟棄。
其實(shí)拆解小程序這種格式并不需要花費(fèi)特別多的精力,因?yàn)槠涓袷奖容^簡(jiǎn)單,而且從下圖流程上來說,后綴為wx的二進(jìn)制格式很可能與wxapkg格式是同源的。
開發(fā)者工具上傳服務(wù)器分發(fā)原始代碼后綴為wx代碼包處理為wxapkg格式包體客戶端
從開發(fā)者工具的代碼中的pack.js很容易發(fā)現(xiàn)一些對(duì)wx格式封裝的痕跡,只不過其中unpack.js的代碼被隱去了。通過實(shí)際的分析發(fā)現(xiàn)(wxapkg文件可以通過截獲網(wǎng)絡(luò)包請(qǐng)求獲得或者在本地的微信appbrand目錄下可以發(fā)現(xiàn)),wxapkg格式就是將wx格式進(jìn)行了轉(zhuǎn)化:Wxml -> Html、 Wxml -> JS、Wxss -> Css,其二進(jìn)制格式跟后綴名為wx二進(jìn)制格式完全一致。我寫了兩個(gè)版本的解析二進(jìn)制包的代碼(Javascript版本傳送門,python版本傳送門),其實(shí)非常簡(jiǎn)單,根據(jù)小程序包結(jié)構(gòu)一步一步解析,基本上沒啥難度。但如果要將Html -> Wxml, JS -> Wxml, Css -> Wxss進(jìn)行還原,其中JS -> Wxml的過程中需要將if語句轉(zhuǎn)變成wx-if標(biāo)簽、for語句轉(zhuǎn)化成wx-for標(biāo)簽有點(diǎn)麻煩,需要對(duì)解析包后的page-frame.html中 JS 代碼進(jìn)行修改,修改細(xì)節(jié)太多就不再詳說了,總之微信小程序的代碼沒有經(jīng)過額外的保護(hù)措施,比較容易進(jìn)行還原。
(PS:暴露一下微信小程序未公開的API,openUrl- 在小程序中打開外部網(wǎng)頁;getGroupInfo- 獲得群的名稱,群內(nèi)成員昵稱等數(shù)據(jù);getOpenidToken - 獲得用戶openid;這些權(quán)限微信應(yīng)該是沒有準(zhǔn)備開放的。每次在進(jìn)入小程序時(shí),客戶端都需要先去請(qǐng)求該小程序的元數(shù)據(jù),例如應(yīng)用名、版本號(hào)、一些權(quán)限列表、代碼包下載地址等描述信息,修改這些元數(shù)據(jù)可以獲得相應(yīng)的權(quán)限,小程序的關(guān)鍵信息完全由后臺(tái)控制進(jìn)行配置,另外小程序的本地文件存儲(chǔ)采用HASH映射機(jī)制進(jìn)行文件定位,文件存儲(chǔ)在外部存儲(chǔ),本身通過自定義算法實(shí)現(xiàn)完整性校驗(yàn) - 首先,小程序最終存儲(chǔ)的文件名是:對(duì)稱加密(文件流內(nèi)容Alder32校驗(yàn)和 | 原始文件名)生成的,最終文件名和文件內(nèi)容會(huì)通過自校驗(yàn)判斷完整性;其次,本地緩存是通過HASH映射查找文件。所以即使能破解文件名和文件內(nèi)容,繞過文件自身簽名校驗(yàn),篡改為攻擊者的偽造文件,小程序APP也無法映射到該偽造文件進(jìn)行使用。)
由上可見,微信并沒有在代碼安全上進(jìn)行過多的考慮。這導(dǎo)致需要在應(yīng)用審核過程中花費(fèi)比較多的功夫,不然作品太容易被復(fù)制竄改,以至于會(huì)失去渠道先機(jī),這對(duì)流量是致命打擊。由于歷史原因,前端的代碼安全技術(shù)發(fā)展的比較緩慢,相比其他被編譯成二進(jìn)制的應(yīng)用,前端這種純文本應(yīng)用,太容易被辨識(shí)與竄改。
對(duì)前端代碼進(jìn)行保護(hù)的目的在于讓機(jī)器容易識(shí)別相關(guān)的指令,而使人難以理解代碼的邏輯,但往往在對(duì)前端代碼進(jìn)行保護(hù)過程中,很難既兼顧指令效率又能使可讀性降低。因此,常常需要在現(xiàn)有的代碼中增加一些額外的驗(yàn)證邏輯,例如一些增加無效的代碼進(jìn)行混淆、采用守護(hù)代碼保護(hù)業(yè)務(wù)代碼不能在其他的域名下正常運(yùn)行、增加一些防止調(diào)試跟蹤的斷點(diǎn)等,這些措施都是使得破解代碼時(shí)人工成本增加,從而增加代碼的安全性。
下面提供一些能夠增加前端代碼安全性的策略:
1. 精簡(jiǎn)(minify)
這是最簡(jiǎn)單且無害的方法,精簡(jiǎn)代碼能減少代碼體積,從而減小數(shù)據(jù)傳輸?shù)呢?fù)荷,同時(shí)也能降低代碼的可讀性。在小程序開發(fā)者工具中也提供該選項(xiàng)。對(duì)Javascript代碼進(jìn)行精簡(jiǎn)大致可以從以下幾個(gè)方面入手:
刪除注釋,刪除無意義或者多余的空白,刪除可以省略的符號(hào)
刪除一些沒有調(diào)用的代碼(Dead code),對(duì)函數(shù)進(jìn)行精簡(jiǎn)(三元運(yùn)算符?:、字符串操作、對(duì)象函數(shù)、對(duì)象繼承、函數(shù)引用、無名函數(shù)、遞歸函數(shù))
將變量名進(jìn)行簡(jiǎn)化,將零散的變量聲明合并,縮短語句
......
常用的工具有很多:YUI Compressor、UglifyJS、Google Closure Compiler、JS Packer、JS Min...
使用工具對(duì)代碼進(jìn)行精簡(jiǎn)時(shí)需要注意:1. 最好備份原始代碼,方便調(diào)試與后期修改。 2. 用于調(diào)試精簡(jiǎn)代碼時(shí)保存的sourcemap,在線上應(yīng)該刪除。 3.編寫代碼的時(shí)候應(yīng)該嚴(yán)格按照規(guī)范,最好使用lint工具對(duì)代碼進(jìn)行檢查,精簡(jiǎn)代碼后導(dǎo)致代碼不可用時(shí),調(diào)試非常困難。
這種簡(jiǎn)單的方法雖然很實(shí)用,但是也很容易被還原出源代碼,使用一些代碼格式化工具可以補(bǔ)齊被刪除的空格、換行、符號(hào)等,例如jsbeautifier。另外2015年就有相關(guān)的研究,從大量的代碼中推測(cè)出被精簡(jiǎn)的代碼,因?yàn)槿藢懘a總有固定的范式,所寫的代碼相似性都非常的高,如果用統(tǒng)計(jì)方式就很容易反推源代碼,蘇黎世聯(lián)邦理工大學(xué)Martin Vechev教授領(lǐng)帶下開發(fā)的工具JSNice就是一款運(yùn)用條件隨機(jī)場(chǎng)(Conditional Random Fields)機(jī)器學(xué)習(xí)和程序分析方法來還原Javascript代碼利器,利用大量的開源代碼,去學(xué)習(xí)命名和類型的規(guī)律。JSNice可以用于以下不同的方面:反精簡(jiǎn)的JavaScript代碼、對(duì)當(dāng)前的代碼提供更多的更有意義的變量名、自動(dòng)化程序的注釋等。相關(guān)論文傳送門 后臺(tái)代碼傳送門
2. 混淆(obfuscation)
混淆可以減低代碼的可讀性,防止被輕易追蹤出程序邏輯。常見的混淆方法有如下幾種:
通過編碼混淆代碼,這篇文章《Javascript常用混淆方法》里面介紹了很多不錯(cuò)的編碼加密方法。但是這些方法有個(gè)明顯缺點(diǎn),增加代碼體積,而且編碼加密都是可逆的。
將標(biāo)識(shí)符混淆和控制邏輯混淆(分離靜態(tài)資源、打亂控制流、增加無義的代碼等),例如aaencode和jjencode。
標(biāo)識(shí)符混淆的方法有多種,有些與編碼混淆代碼方法有些重疊,常用方法有哈希函數(shù)命名、標(biāo)識(shí)符交換和重載歸納等。哈希函數(shù)命名是簡(jiǎn)單地將原來標(biāo)識(shí)符的字符串替換成該字符串的哈希值,這樣標(biāo)識(shí)符的字符串就與軟件代碼不相關(guān)了;標(biāo)識(shí)符交換是指先收集軟件代碼中所有的標(biāo)識(shí)符字符串,然后再隨機(jī)地分配給不同的標(biāo)識(shí)符,該方法不易被攻擊者察覺;重載歸納是指利用高級(jí)編程語言命名規(guī)則中的一些特點(diǎn),例如在不同的命名空間中變量名可以相同,使代碼中不同的標(biāo)識(shí)符盡量使用相同的字符串,增加攻擊者對(duì)軟件源代碼的理解難度。
控制混淆是改變程序的執(zhí)行流程,從而打斷逆向分析人員的跟蹤思路,達(dá)到保護(hù)軟件的目的。一般采用的技術(shù)有插入指令、偽裝條件語句、斷點(diǎn)等。偽裝條件語句是當(dāng)程序順序執(zhí)行從A到B,混淆后在A和B之間加入條件判斷,使A執(zhí)行完后輸出TRUE或FALSE,但不論怎么輸出,B一定會(huì)執(zhí)行。控制混淆采用比較多的還有模糊謂詞、內(nèi)嵌外聯(lián)、打破順序等方法。模糊謂詞是利用消息不對(duì)稱的原理,在加入模糊謂詞時(shí)其值對(duì)混淆者是已知的,而對(duì)反混淆者卻很難推知。所以加入后將干擾反匯編者對(duì)值的分析。模糊謂詞的使用一般是插入一些死的或不相關(guān)的代碼(bogus code),或者是插入在循環(huán)或分支語句中,打斷程序執(zhí)行流程。內(nèi)嵌(in-line)是將一小段程序嵌入到被調(diào)用的每一個(gè)程序點(diǎn),外聯(lián)(out-line)是將沒有任何邏輯聯(lián)系的一段代碼抽象成一段可被多次調(diào)用的程序。打破順序是指打破程序的局部相關(guān)性。由于程序員往往傾向于把相關(guān)代碼放在一起,通過打破順序改變程序空間結(jié)構(gòu),將加大破解者的思維跳躍[3]。
另外還有些混淆方式是專門針對(duì)于反混淆工具設(shè)計(jì)的,這就需要去仔細(xì)分析反混淆工具的原理,在一些特定的地方插入代碼使反混淆器進(jìn)入死循環(huán)或者異常跳出。
一般來說,提供代碼精簡(jiǎn)的工具都會(huì)提供一些混淆的方法,除此之外,比較知名的商業(yè)工具有jasob、jscrambler,一般越商業(yè)的越難被反混淆,然而這些高級(jí)的代碼混淆也常會(huì)被用于隱藏應(yīng)用中的惡意代碼。對(duì)惡意代碼進(jìn)行混淆是為了躲避殺毒軟件的檢測(cè),這些代碼在被混淆擴(kuò)充后會(huì)難以被識(shí)別為惡意軟件。相應(yīng)的也有一些反混淆的工具出現(xiàn),例如上面提到的JSNice工具能夠?qū)煜拇a進(jìn)行推理,另外反混淆工具JSDetox專門針對(duì)一些混淆方法做過專門的支持。反混淆一直是一項(xiàng)體力活,根據(jù)不同的混淆策略需要進(jìn)行反推演算,這就是一場(chǎng)攻與防的游戲罷了。
3. 加密(encryption)
加密的關(guān)鍵思想在于將需要執(zhí)行的代碼進(jìn)行一次編碼,在執(zhí)行的時(shí)候還原出瀏覽器可執(zhí)行的合法的腳本,在某個(gè)角度也可以認(rèn)為是一種混淆的形式,看上去和可執(zhí)行文件的加殼有點(diǎn)類似。Javascript提供了將字符串當(dāng)做代碼執(zhí)行(evaluate)的能力,可以通過 Function constructor、eval、setTimeout、setInterval、Worker、DOM event等將字符串傳遞給JS引擎進(jìn)行解析執(zhí)行,由于有些需要用到eval函數(shù),會(huì)導(dǎo)致代碼性能會(huì)減低。以Worker執(zhí)行舉例:
var URL=window.URL || window.webkitURL;var Blob=window.Blob || window.webkitBlob;var blobURL=URL.createObjectURL( new Blob(['console.log("Hello World!")'], {type: 'application/javascript'}));new Worker(blobURL);URL.revokeObjectURL(blobURL);
有以下常見的幾種加密方法:
base64編碼,一種簡(jiǎn)單的方法就是將代碼轉(zhuǎn)化成base64編碼,然后通過atob以及eval進(jìn)行解碼然后運(yùn)行,另外一種采用base62編碼技術(shù)更為常見,其最明顯的特征是生成的代碼以eval(function(p,a,c,k,e,r))開頭。無論代碼如何進(jìn)行變形,其最終都要調(diào)用一次eval等函數(shù)。解密的方法不需要對(duì)其算法做任何分析,只需要簡(jiǎn)單地找到這個(gè)最終的調(diào)用,改為console.log或者其他方式,將程序解碼后的結(jié)果按照字符串輸出即可。
(PS: 從算法上看,packer是一種base64編碼字典壓縮策略,packer的base64編碼的壓縮率很高,精簡(jiǎn)后代碼依然可以減少50%體積以上,因?yàn)閹в薪鈮浩骱妥址恚介L(zhǎng)的代碼理論上壓縮率更高,想要了解詳情可以看看這篇文章《Packer,你對(duì)我的JS做了什么!》)
使用復(fù)雜化表達(dá)式,在Javascript中可以把原本簡(jiǎn)單的字面量(Literal)、成員訪問(MemberExpression)、函數(shù)調(diào)用(CallExpression)等代碼片段變得難以閱讀。例如這個(gè)方法僅用+!等符號(hào)就足以實(shí)現(xiàn)幾乎任意Javascript代碼。在 JS 代碼中可以找到許多這樣互逆的運(yùn)算,通過使用隨機(jī)生成的方式將其組合使用,可以把簡(jiǎn)單的表達(dá)式無限復(fù)雜化。
隱寫術(shù),將 JS 代碼隱藏到了特定的介質(zhì)當(dāng)中。如通過最低有效位(LSB)算法嵌入到圖片的 RGB 通道、隱藏在圖片 EXIF 元數(shù)據(jù)、隱藏在 HTML 空白字符、放到css文件中(利用content樣式能存放字符串的特性)等。比如一張圖片黑掉你:在圖片中嵌入惡意程序,這個(gè)正是使用了最低有效位平面算法,結(jié)合HTML5的canvas或者處理二進(jìn)制數(shù)據(jù)的TypeArray,抽取出載體中隱藏的數(shù)據(jù)(如代碼)。隱寫的方式同樣需要解碼程序和動(dòng)態(tài)執(zhí)行,所以破解的方式和前者相同,在瀏覽器上下文中劫持替換關(guān)鍵函數(shù)調(diào)用的行為,改為文本輸出即可得到載體中隱藏的代碼[4]。
混合加密,單個(gè)方法容易被破解,但組合起來就不會(huì)那么容易了,破解成本也會(huì)指數(shù)增長(zhǎng),例如jdists采用組合加密和嵌套加密的方式。
這些加密的方式都很容易通過對(duì)源代碼進(jìn)行詞法分析、語法分析進(jìn)行還原,首先將代碼的字符串轉(zhuǎn)換為抽象語法樹(Abstract Syntax Tree, AST)的數(shù)據(jù)形式,然后從語法樹的根節(jié)點(diǎn)開始,使用深度優(yōu)先遍歷整棵樹的所有節(jié)點(diǎn),根據(jù)節(jié)點(diǎn)上分析出來的指令逐個(gè)執(zhí)行,直到腳本結(jié)束返回結(jié)果。這種方法大多數(shù)用于對(duì)代碼進(jìn)行優(yōu)化,例如最近Facebook開源了代碼優(yōu)化工具Prepack,可以自動(dòng)消除冗余代碼,降低打包體積和執(zhí)行時(shí)間,基本上就可以用來將這些加密的字符串進(jìn)行還原,畢竟編碼這些字符串都是可以通過詞法語法推測(cè)出來的。
4. 編譯(compile)
Github上有一份清單記錄了所有Javascript擴(kuò)展語言,這些語言都可以通過編譯器轉(zhuǎn)化為Javascript語言,這也是前端發(fā)展的一個(gè)趨勢(shì),原來寫的html,css,Javascript已經(jīng)開始變成了一個(gè)“中間語言”,而且越來越多的團(tuán)隊(duì)也有了自己的一套前端編譯系統(tǒng)。Javascript越來越像Web中的匯編語言,特別是近些年Node的普及,讓前端變得越來越復(fù)雜,大量前端框架的出現(xiàn),使得Javascript代碼可以通過手工編寫,也可以從另一種語言編譯而來,詳情參考幾年前Brendan Eich(JavaScript之父)、Douglas Crockford(JSON之父),還有Mike Shaver(Mozilla技術(shù)副總裁)的郵件。通過編譯后的Javascript代碼越方便機(jī)器的理解,降低可讀性,在某一定角度上講,這也不愧為一種代碼保護(hù)措施。據(jù)說幾大科技巨頭正在醞釀給瀏覽器應(yīng)用設(shè)計(jì)一款通用的字節(jié)碼標(biāo)準(zhǔn)——WebAssembly,一旦這個(gè)設(shè)想得以實(shí)現(xiàn),代碼保護(hù)將可以引入真正意義上的“加殼”或者虛擬機(jī)保護(hù),對(duì)抗技術(shù)又將提升到一個(gè)新的臺(tái)階。目前在桌面端,使用NW.js框架可以JavaScript應(yīng)用程序的源代碼可以被編譯為本地代碼,在運(yùn)行時(shí)通過NW.js動(dòng)態(tài)還原出源代碼,但是這種方法目前會(huì)比正常的JS代碼慢30%左右。
5. 防止被調(diào)試
對(duì)代碼進(jìn)行破解分析無非分為靜態(tài)分析和動(dòng)態(tài)分析,如果對(duì)代碼進(jìn)行混淆加密等形式操作,那么靜態(tài)分析就很麻煩了,對(duì)代碼調(diào)試跟蹤分析可以對(duì)代碼整體邏輯有一個(gè)宏觀的把控。例如首先判斷瀏覽器是否開啟了開發(fā)者工具控制臺(tái)(目前最完美的解決方案?jìng)魉烷T),如果檢測(cè)出控制臺(tái)開啟則堵塞Javascript執(zhí)行或讓代碼異常跳出。另外Android 4.4及以上和iOS是支持webkit remote debug的,因此應(yīng)該在debug模式下,設(shè)置代碼可以被debug,release模式下,禁止debug以保護(hù)數(shù)據(jù)安全。
6. 前后端協(xié)作
首先得強(qiáng)調(diào)的事情是不要在前端放敏感數(shù)據(jù),前端容易破解,因此需要配合后端進(jìn)行安全防護(hù),例如微信小程序的登錄,必須利用授信的后端配合才能完成此項(xiàng)功能,另外在小程序的網(wǎng)絡(luò)請(qǐng)求中的referer是不可以設(shè)置的,格式固定為https://servicewechat.com/{appid}/{version}/page-frame.html,其中{appid}為小程序的appid,通過驗(yàn)證appid字段可以抵御一些直接的山寨,其次就是加快迭代速度更改代碼保護(hù)策略,這樣可以讓之前的分析失效,增加破解的成本。
以上就是對(duì)當(dāng)前前端代碼安全進(jìn)行的探索,最后用一句話結(jié)束:
Beneath this mask, there is more than flesh. Beneath this mask, there is an idea. And ideas are bulletproof.
作者:不詳
出處:知識(shí)商店
運(yùn)行環(huán)境不同
網(wǎng)頁 —— 瀏覽器(內(nèi)核渲染)
小程序 —— 微信環(huán)境API不同
由于運(yùn)行環(huán)境不同,小程序無法調(diào)用DOM和BOM的API
但是小程序可以調(diào)用微信客戶端的API,如定位,掃碼支付等開發(fā)模式不同
網(wǎng)頁開發(fā)模式: 瀏覽器 + 代碼編輯器 (用記事本都可以敲出一個(gè)靜態(tài)頁面)
小城開發(fā)流程:1. 注冊(cè)開發(fā)賬號(hào) 2. 安裝小程序開發(fā)工具 3. 創(chuàng)建與配置小程序
相比較之下,小程序上手比較麻煩。
使用瀏覽器打開 https://mp.weixin.qq.com/ 網(wǎng)址,點(diǎn)擊右上角的“立即注冊(cè)”即可進(jìn)入到小程序開發(fā)賬號(hào)進(jìn)行注冊(cè)。
點(diǎn)擊注冊(cè)小程序 -> 填寫賬號(hào)信息 -> 填寫賬號(hào)信息 ->點(diǎn)擊鏈接激活賬號(hào) ->選擇主體類型(這里選擇為個(gè)人即可) -> 主體信息登記 - > 重點(diǎn): 獲取小程序自己的AppID,注冊(cè)后在開發(fā)設(shè)置即可找到
小程序開發(fā)工具 是微信所推薦的開發(fā)工具
她所提供有主要功能
快速創(chuàng)建小程序項(xiàng)目
對(duì)小程序項(xiàng)目代碼進(jìn)行編寫
進(jìn)行編譯和預(yù)覽
上傳代碼發(fā)布
推薦下載和安裝最新的穩(wěn)定版(Stable Build)的微信開發(fā)者工具,下載頁面的鏈接如下:
https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
效果:
pages 存放頁面的文件
utils 存放工具性質(zhì)的模塊(腳本文件,如wxs腳本過濾文件)
app.js 小程序的入口文件
app.json 小程序的全局配置(配置窗口樣式版本,窗口路徑,tabBar導(dǎo)航條等)
app.wxss 小程序的全局樣式
project.config.json 小程序的項(xiàng)目配置
sitemap.json 設(shè)置小程序可否被搜索到
每一個(gè)頁面都有四個(gè)文件
.js 腳本文件(生命周期函數(shù),存放數(shù)據(jù),自定義函數(shù))
.wxml 頁面結(jié)構(gòu)文件 (編輯頁面UI結(jié)構(gòu))
.wxss 頁面樣式文件 (美化頁面樣式)
.json 頁面配置文件 (配置頁面,如當(dāng)前窗口的外觀,引用自定義組件)
json 是一種數(shù)據(jù)格式,在實(shí)際開發(fā)中,json文件總是以配置文件存在,在小程序中也不例外
app.json 是關(guān)于項(xiàng)目的配置文件可以配置:
window:全局定義小程序所有窗口樣式(導(dǎo)航條,背景色,文字樣式等)
page:頁面路徑配置(創(chuàng)建頁面)
style:全局定義樣式版本設(shè)置
sitemaplocation: 用來指明 sitemap.json 的位置
只需要在 app.json -> pages 中新增頁面的存放路徑,小程序開發(fā)者工具即可幫我們自動(dòng)創(chuàng)建對(duì)應(yīng)的頁面文件,(放在第一位置路徑的頁面即為小程序主頁面 這里為 index頁面)
如圖所示:
這個(gè)是整個(gè)項(xiàng)目的配置文件,用來配置記錄我們對(duì)項(xiàng)目開發(fā)的個(gè)性化設(shè)置,如
setting:編譯配置(如:設(shè)置是否檢查sitemap)
appid:自己的appID
projectname: 項(xiàng)目名稱
微信現(xiàn)已開放小程序內(nèi)搜索,效果類似于 PC 網(wǎng)頁的 SEO。
sitemap.json 文件用來配置小程序頁面是否允許能被搜索到
當(dāng)開發(fā)者允許索引時(shí),微信會(huì)以爬蟲的形式,為小程序的內(nèi)容和項(xiàng)目名稱作為索引,用戶通過搜索關(guān)鍵字即可查到對(duì)應(yīng)小程序
"action":"allow"
頁面默認(rèn)是被允許索引的,要取消索引只需要設(shè)置為disallow
頁面的配置文件(局部配置)
可以用來配置當(dāng)前頁面的窗口樣式,組件引用,與app.json的類似,
在page.json的配置項(xiàng)會(huì)覆蓋全局樣式app.json的配置項(xiàng)
WXML(WeiXin Markup Language)是小程序框架設(shè)計(jì)的一套標(biāo)簽語言,用來構(gòu)建小程序頁面的結(jié)構(gòu),其作用類似于網(wǎng)頁開發(fā)中的 HTML。
WXML 和 HTML 的區(qū)別
① 標(biāo)簽名稱不同
HTML (div, span, img, a)WXML(view, text, image, navigator)
② 屬性節(jié)點(diǎn)不同
< a href=“#”>超鏈接< navigator url=“/pages/home/home”>
③ 提供了類似于 Vue 中的模板語法
數(shù)據(jù)綁定列表渲染條件渲染
WXSS (WeiXin Style Sheets)是一套樣式語言,用于描述 WXML 的組件樣式,類似于網(wǎng)頁開發(fā)中的 CSS(cascading style sheet)。
WXSS 和 CSS 的區(qū)別
① 新增了rpx單位
CSS 中需要手動(dòng)進(jìn)行像素單位換算,例如 remWXSS 在底層支持新的尺寸單位 rpx,在不同大小的屏幕上小程序會(huì)自動(dòng)進(jìn)行換算
② 提供了全局的樣式和局部樣式
項(xiàng)目根目錄中的 app.wxss 會(huì)作用于所有小程序頁面局部頁面的 .wxss 樣式僅對(duì)當(dāng)前頁面生效
③ WXSS 僅支持部分 CSS 選擇器
.class 和 #idelement并集選擇器、后代選擇器::after 和 ::before 等偽類選擇器
在小程序中,我們通過 .js 文件來處理用戶的操作。例如:響應(yīng)用戶的
點(diǎn)擊、獲取用戶的位置等等
小程序中的 JS 文件分為三大類(其他:自定義組件componnet),分別是:
① app.js
是整個(gè)小程序項(xiàng)目的入口文件,通過調(diào)用 App() 函數(shù)來啟動(dòng)整個(gè)小程序
② 頁面的 .js 文件
是頁面的入口文件,通過調(diào)用 Page() 函數(shù)來創(chuàng)建并運(yùn)行頁面
③ 普通的 .js 文件
是普通的功能模塊文件,用來封裝公共的函數(shù)或?qū)傩怨╉撁媸褂?/span>
js(java script) 是一個(gè)實(shí)現(xiàn)業(yè)務(wù)邏輯的文件。
例如:Andriod安卓系統(tǒng) 和 IOS蘋果系統(tǒng),是兩個(gè)不同的宿主環(huán)境,
安卓的應(yīng)用必須要在安卓系統(tǒng)才能運(yùn)行,這也是為什么 之前有些軟件 安卓和蘋果不能兼容了。
而小程序的宿主環(huán)境則是微信,小程序只能在微信上運(yùn)行,小程序借助宿主環(huán)境提供的能力,可以完成許多普通網(wǎng)頁無法完成的功能,例如:微信掃碼、微信支付、微信登錄、地理定位、etc
小程序的宿主環(huán)境微信所包含的內(nèi)容
通信模式運(yùn)行機(jī)制組件API
小程序中通信的主體是渲染層和邏輯層,其中:
① WXML 模板和 WXSS 樣式工作在渲染層
② JS 腳本工作在邏輯層
小程序中的通信模型分為兩部分:
① 渲染層和邏輯層之間的通信
由微信客戶端進(jìn)行轉(zhuǎn)發(fā)
② 邏輯層和第三方服務(wù)器之間的通信
由微信客戶端進(jìn)行轉(zhuǎn)發(fā)
微信客戶端將代碼包下載到本地
解析app.json 全局配置文件
執(zhí)行小程序入口文件app.js,即調(diào)用app.js 的App實(shí)例(相當(dāng)于一個(gè)類)
渲染小程序首頁
解析page.json局部配置文件
執(zhí)行頁面入口文件page.js,即調(diào)用page.js 的page()創(chuàng)建頁面實(shí)例
加載.wxml和.wxss 結(jié)構(gòu)和樣式文件
恭喜你!!成功揚(yáng)起小程序的揚(yáng)帆!!!
信小程序初步入坑小指南
https://developers.weixin.qq.com/miniprogram/dev/devtools/beta.html
打開鏈接下載小程序云開發(fā)
為json格式的文件,為一個(gè)配置文件,屬于全局的
初始化的文件內(nèi)容
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
pages
各個(gè)參數(shù)的解釋,pages為頁面,每一次更改頁面,增加或者新增加頁面都需要修改pages參數(shù)。不需要加后綴名,微信框架會(huì)自動(dòng)添加后綴名。
window
對(duì)于全局導(dǎo)航欄的設(shè)置。
navigationBarBackgroundColor 設(shè)置全局的導(dǎo)航欄的顏色
navigationBarTitleText 設(shè)置導(dǎo)航欄的文字內(nèi)容
navigationStyle 設(shè)置導(dǎo)航欄的樣式
backgroundColor 設(shè)置窗體的顏色,即下拉刷新透露出的顏色
即需要設(shè)置 "enablePullDownRefresh": true, 其布爾值為true即可進(jìn)行漏出設(shè)置的窗體顏色。
backgroundTextStyle 設(shè)置下拉的loding樣式
tabBar
是下方的導(dǎo)航欄的設(shè)置。這個(gè)直接看文檔吧。。
https://developers.weixin.qq.com/miniprogram/dev/framework/config.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE
app.json文件如下
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"dark",
"navigationBarBackgroundColor": "#c7dbc8",
"navigationBarTitleText": "小小",
"navigationBarTextStyle":"whiter",
"enablePullDownRefresh": true,
"backgroundColor": "#fceaea"
},
"tabBar": {
"list": [{
"text": "ming",
"pagePath": "pages/logs/logs"
}, {
"text":"home",
"pagePath": "pages/index/index"
}],
"color": "#f8fcea",
"backgroundColor": "#ff9999",
"selectedColor": "#c5ff99",
"borderStyle": "white",
"position": "top"
},
"functionalPages": true
}
該文件為一個(gè)本地的配置文件
就是對(duì)于微信的一些設(shè)置
wxml ≈ html
感覺很像ejs,好吧
wxml中內(nèi)容
<text>{{mesg}}</text>
在同路下的js文件中
Page({
data: {
msg: "hello world"
}
})
渲染出來的結(jié)果
Page({
data: {
msg: "hello world"
},
clickMe: function() {
this.setData({msg: "3333"});
}
})
wxml
<text>{{msg}}</text>
<button bindtap="clickMe">點(diǎn)擊按鈕</button>
點(diǎn)擊按鈕將會(huì)自動(dòng)更新頁面的數(shù)據(jù)
客戶端打開小程序之前,會(huì)把小程序全部下載到本地。
通過app.json可以知道頁面的啟動(dòng)地址。
小程序只有一個(gè)app的實(shí)例,全局共享。
啟動(dòng)完成后觸發(fā)onLaunch事件,然后運(yùn)行回調(diào)函數(shù)
在小程序啟動(dòng)完畢以后控制臺(tái)輸出內(nèi)容
App({
onLaunch: ()=> {
console.log('小程序啟動(dòng)完畢')
}
})
上方進(jìn)行了一次回調(diào)
一個(gè)頁面有四個(gè)文件,分別是json(配置文件),wxml(頁面文件,類似于html),js文件(處理頁面的相關(guān)交互,和網(wǎng)頁類似)
js中有一個(gè)page,為一個(gè)頁面的構(gòu)造器,渲染頁面的時(shí)候先裝載json文件,配置當(dāng)前的頂部導(dǎo)航,接著裝載wxml文件,配置頁面的DOM,在裝載wxss,進(jìn)行對(duì)頁面樣式的處理
和網(wǎng)頁的類似,都是同樣的
最后將會(huì)讀取js文件,根據(jù)頁面中的page函數(shù)即構(gòu)造器中的內(nèi)容,將wxml和data進(jìn)行綁定,渲染出結(jié)果,為mvvm
mvc 分別是模型層,視圖層,和控制器,當(dāng)用戶請(qǐng)求到達(dá)以后,將會(huì)先經(jīng)過路由,即入口文件,即主文件中的server.js文件,接著進(jìn)入lib目錄下的route.js文件,對(duì)路由進(jìn)行分發(fā),路由在將數(shù)據(jù)傳遞給控制器,controller ,controller 收到請(qǐng)求以后再向model 索要數(shù)據(jù),索要完成以后,在將數(shù)據(jù)導(dǎo)向view層,即ejs文件的地方,渲染完成文件以后返回給用戶。 mvp 在mvc的基礎(chǔ)上,view中不寫邏輯,,在原先控制器的地方完成頁面的合并
mvvm 和mvp類似,只不過view和原先的控制器雙向綁定,即使用get 和 set方式,達(dá)到當(dāng)數(shù)據(jù)更改的時(shí)候,進(jìn)行回調(diào)
是滴,小程序采用組件化,例如生成地圖map即可
ps 在網(wǎng)頁中,生成地圖,需要引入第三方的js文件,以及第三方的api,達(dá)到生成地圖的目的。
api的回調(diào)為異步操作,所以呢,依舊要進(jìn)行回調(diào)
發(fā)布者-訂閱模型
小程序使用的是js引擎進(jìn)行渲染,邏輯層將數(shù)據(jù)發(fā)送給視圖層,視圖層接受事件的反饋,開發(fā)者寫的所有文件都會(huì)打包成為一份js文件,小程序運(yùn)行時(shí)啟動(dòng),小程序離開時(shí)銷毀,
吐槽 一些瀏覽器里的js在微信小程序無法使用,小程序還有npm? 天哪,
注冊(cè)程序
app()函數(shù),必須在app.js文件中調(diào)用,接受一個(gè)object的參數(shù)
前臺(tái)后臺(tái)定義,當(dāng)用戶點(diǎn)擊左上角關(guān)閉的時(shí)候, 或者按住home離開微信,小程序,沒有銷毀,將會(huì)進(jìn)入后臺(tái),再次打開進(jìn)入前臺(tái),當(dāng)小程序進(jìn)入后臺(tái)一段時(shí)間以后,系統(tǒng)資源占用過高將會(huì)不定時(shí)的銷毀
onLaunch
代碼如下
//app.js
App({
onLaunch: (onlaunch)=> {
console.log(onlaunch)
}
})
效果如下
即獲取頁面的參數(shù)
相比較網(wǎng)頁好輕松,,網(wǎng)頁還需要進(jìn)行先字符串分隔,然后再次以=進(jìn)行分隔,然后將其循環(huán),遍歷該數(shù)組,將其保存進(jìn)入對(duì)象,完成。如果使用json字符串進(jìn)行傳,可能稍微方便一點(diǎn)
getAPP
getApp函數(shù)能獲取小程序的各種函數(shù),即onLaunch等其他的一些函數(shù)
即獲取到小程序的一個(gè)實(shí)例
注冊(cè)頁面
page為一個(gè)構(gòu)造函數(shù),接受對(duì)象,用來對(duì)頁面進(jìn)行初始化
data
data和渲染層,進(jìn)行數(shù)據(jù)的綁定
onLoad
進(jìn)行參數(shù)的傳值
Page({
data: {
msg: "hello world"
},
clickMe: function() {
this.setData({msg: "3333"});
},
onLoad: (query)=> {
console.log(query);
}
})
onShow
頁面切入的時(shí)候,將會(huì)進(jìn)行顯示。
例: 按住home按鍵,在回到小程序界面的時(shí)候,將會(huì)回調(diào)該注冊(cè)的函數(shù)
onReady
頁面渲染完成以后,將會(huì)回調(diào)該函數(shù)
onHide
頁面切換的時(shí)候,將會(huì)回調(diào)注冊(cè)的函數(shù)
onUnload
頁面卸載的時(shí)候,將會(huì)觸發(fā)
頁面事件
onPullDownRefresh
用戶下拉,需要設(shè)置onReachBottomDistance,即用戶下拉到頁面底部多少的時(shí)候,觸發(fā)該事件
onPageScroll
用戶滑動(dòng)的距離,沒有正負(fù)之分
onShareAppMessage
用戶轉(zhuǎn)發(fā)的接口
在button組件中設(shè)置
open-type="share"
即可設(shè)置為轉(zhuǎn)發(fā)按鈕
需要有return進(jìn)行返回參數(shù)
onTabItemTap
單擊tab將會(huì)觸發(fā)該內(nèi)容
onTabItemTap: (item)=>{
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
}
如果按住導(dǎo)航,將會(huì)進(jìn)行輸出
當(dāng)單擊組件的時(shí)候,發(fā)生事件
這一點(diǎn)終于和網(wǎng)頁類似了。網(wǎng)頁中也可以實(shí)現(xiàn)一個(gè)元素和事件進(jìn)行相互的綁定
viewTap: ()=> {
console.log('您已經(jīng)單擊按鈕')
}
<button bindtap="viewTap">這是按鈕</button>
Page.route
當(dāng)前頁面的路徑,類似于網(wǎng)頁的
window.location.href
可以獲取到當(dāng)前頁面的url
onShow: function() {
console.log('頁面進(jìn)行顯示切入前臺(tái)');
console.log(this.route);
},
當(dāng)用戶切換tab的時(shí)候,將會(huì)立馬輸出當(dāng)前頁面的path值
其中this指代當(dāng)前的page,因?yàn)槭窃谝粋€(gè)page函數(shù)內(nèi)部
Page.prototype.setData
為page的繼承函數(shù),將數(shù)據(jù)從邏輯層發(fā)送到視圖層(異步),this.data的值,(同步
)
ps 單純的改變this.data的值,不會(huì)起作用,因?yàn)轫撁嬉呀?jīng)渲染完成,需要進(jìn)行發(fā)送到視圖層,進(jìn)行更新視圖
ps 是的。小程序使用的就是mvvm
js
Page({
data: {
text0: "11111",
text1: "22222",
text2: "333333",
text3: "444444"
},
changeText0: function() {
this.setData({text0: "22222"})
},
changeText1: function () {
this.setData({ text1: "33333" })
},
changeText2: function () {
this.setData({ text2: "44444" })
},
changeText3: function () {
this.setData({ text3: "55555" })
},
})
wxml
<view>{{text0}}</view>
<button bindtap="changeText0">更改上方文字</button>
<view>{{text1}}</view>
<button bindtap="changeText1">更改上方文字</button>
<view>{{text2}}</view>
<button bindtap="changeText2">更改上方文字</button>
<view>{{text3}}</view>
<button bindtap="changeText3">更改上方文字</button>
路由
小程序中的路由是有框架達(dá)到的
框架用棧的方式維護(hù)了當(dāng)前的所有頁面
ps 又見到棧了
getCurrentPages
該函數(shù)用于獲取當(dāng)前頁面的棧,返回的是一個(gè)數(shù)組
適用于獲取上一個(gè)返回的頁面
全局變量
js文件中聲明的變量,和函數(shù)只在文件中有用,不同文件可以聲明相同的
ps 如果加載到一個(gè)頁面的時(shí)候,將會(huì)發(fā)生命名沖突
可以在app.js文件中設(shè)置全局的數(shù)據(jù)
// a.js
var app=getApp();
console.log(app.globalData)
//app.js
App({
onLaunch: (onlaunch)=> {
console.log(onlaunch)
},
globalData: 333
})
模塊化
類似于node.js的包
使用的同樣是exports進(jìn)行接口的暴露
是滴,類比node.js即可,是滴,小程序還不支持引入npm包
然后就可以歡快的引入npm包了。
ps 貌似有個(gè)小坑。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。