微信小程序包含下面四種文件:
<view> <text class="window">{{ text }}</text> </view> Page({ data:{ text:"這是一個頁面" }, onLoad:function(options){ // 頁面初始化 options為頁面跳轉所帶來的參數 }, // ........ })
微信小程序只能通過其 mvvm 的模板語法來動態改變頁面,本身 js 并不支持 BOM 和 DOM 操作。
在 mac 端直接解壓應用 發現 app.nw 文件夾,即開發工具源碼。可以知道該項目由 nw.js 編寫; 在 package.json 文件下找到應用入口:app/html/index.html。入口 js 為 dist/app.js 我們可以看到整個編輯器的大致邏輯。
但我們關心的是構建過程,在 weapp 文件夾下存在 build.js 文件。沒有找到有用的信息,只看到了 upload 模塊,包括對大小限制,上傳包命名。
為此懷疑,微信小程序本身和 RN 類似。是在服務端打包成 native 語言的。但是通過 android 邊框測試發現,微信小程序根本不是 native 原生內容。
原生界面效果:
繼續在 trans 文件夾下發現了編譯模板。
用到的內容:
在模板中,我們發現使用了 WAWebview.js 文件,WAService.js文件。 在 transWxmlToJs 中我們發現一段 generateFuncReady 事件的函數。對比注冊該事件的函數在 WAWebview.js 中。
我們嘗試使用 wcc 對input.xml 文件進行編譯。
wcc -d input.xml
生成了一段腳本:
window.__wcc_version__ = 'v0.6vv_20161230_fbi' var $gwxc var $gaic = $gwx = function (path, global) { function _(a, b) { b && a.children.push(b); } ....
通過代碼我們發現,調用 $gwx 函數會再生成一個有返回值的函數(前提是 path 填寫正確);于是我們執行如下代碼:
$gwx("input.xml")("test")
得出如下內容:
{ "tag": "wx-page", "children": [ { "tag": "wx-view", "attr": { "class": "section" }, "children": [ { "tag": "wx-input", "attr": { "autoFocus": true, "placeholder": "這是一個可以自動聚焦的input" }, "children": [] } ] } ] }
這應該是一個類似 Virtual dom 的對象,交給了 WAWebivew.js 來渲染,標簽名為 wx-view, wx-input。
我們可以在代碼里面發現,wx 下注冊的 api 最終都會調用 WeixinJSBridge 方法。這個方法應該是在打包的時候端上注入的。我們也可以在 WAServeice.js 中找到該方法的定義。
所以我們得到了一個結論,WAService.js 是編輯器用來接受 wx 方法回調的代碼。
這里我們觀察到,組件:wx-video, wx-canvas, wx-contact-button, wx-map, wx-textarea 等 behaviors 都含有 "wx-native" 屬性。這是不是意味著,這類組件都是 native 原生實現的呢。我們打開邊框檢查,發現這類組件確實都是原生的組件。
綜上,微信小程序的界面有部分組件使用原生方式實現的,native 組件層在 WebView 層之上。大部分還是用前端實現的,這樣解釋了微信小程序的一個bug。
微信官方文檔:
因為 scroll-view 是前端實現,在里面使用 native 組件,這樣就無法監聽滾動了。
組件是需要數據來渲染的,查看文檔我們知道發送請求的 api 為 wx.request;通過上面分析,我們知道 wx.request 實際調用的是 WeixinJSBridge。現在我們看看 WeixinJSBridge
WeixinJSBridge 真正發送處理數據請求的是這段代碼;如果當前環境是 ios, 那么調用 WKWebview 的 window.webkit.messageHandlers.invokeHandler.postMessage。如果所處環境是 android 則調用 WeixinJSCore.invokeHandler (調用的時候,默認會帶上當前 webviewID)。
在對 WeixinJSBridge.js 分析中,我們并沒有發現前端的通訊功能,路由能力,數據綁定等內容。進一步查看找到了一個 WAService.js 文件。 查看 WAService.js 文件源碼:
綜上,WAService.js 主要實現的功能:
到這里我們得出結論,小程序的架構方案:
整個小程序由兩個 webview 組成,代碼分為 UI 層和邏輯層。UI 層運行在第一個 WebView 當中,執行 DOM 操作和交互事件的響應,里面是 WAWebview.js 代碼及編譯后的內容。邏輯層執行在(第二個webview 中)獨立的 JS 引擎中(iOS:JavaScriptCore, android:X5 JS解析器;統稱 JSCore;開發工具中,nwjs Chrome 內核),WAService.js 代碼和業務邏輯。
當我們對 view 層進行事件操作后,會通過 WeixinJSBridge 將數據傳遞到 Native 系統層。Native 系統層決定是否要用 native 處理,然后丟給 邏輯層進行用戶的邏輯代碼處理。邏輯層處理完畢后會將數據通過 WeixinJSBridge 返給 View 層。View 渲染更新視圖。
微信的這種架構,對邏輯和UI進行了完全隔離,小程序邏輯和UI完全運行在2個獨立的Webview里面來處理。那么這么做的好處是啥?總感覺更加麻煩了。除了小程序外,還有人采用這種架構設計么?
在網上搜索了一下,目前使用這種架構的項目還真有一個:去哪兒最新的 YIS 框架
YIS 采取了類似小程序的架構,分為邏輯層和UI層。UI 層運行在 WebView 中,而邏輯層運行在獨立的 JS 引擎中。相應地,整個應用的代碼,也分為兩個大的部分,一部分運行在 WebView 中,一部分運行在JS引擎中。JS引擎計算DOM結構輸出給WebView,WebView轉發用戶的點擊事件給JS引擎。
該項目做法和小程序十分類似,唯一缺少的就是沒有 native 的組件吧。然而官方文檔上也沒有任何介紹,為什么要這么做,只是說更流暢了。
傳統 web 頁面顯示需要經歷一下幾個步驟:
而利用小程序架構后,我們就可以將上述過程拆解成兩部分并行執行: webview 部分:
jscore 部分:
這樣渲染進程和邏輯進程分離,并行處理:加速首屏渲染速度;避免單線程模型下,js 運算時間過長,UI 出現卡頓。 完全采用數據驅動的方式,不能直接操作 DOM,利用定制開發規范的方式避免低質量的代碼的出現。
當然這種架構方案也有一定的缺點:
點贊+轉發,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓-_-)
關注 {我},享受文章首發體驗!
每周重點攻克一個前端技術難點。更多精彩前端內容私信 我 回復“教程”
原文鏈接:http://eux.baidu.com/blog/fe/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E6%9E%B6%E6%9E%84%E5%8E%9F%E7%90%86
作者:田光宇
者 | 伍杏玲
出品 | CSDN(ID:CSDNnews)
在今年 Stack Overflow 的開發者調查報告里,我們發現一項有趣的數據:調查的 9 萬名開發者中,70.8% 的開發者早在 18 歲前便寫下第一行代碼。
結合今年教育部新公布的《2019年教育信息化和網絡安全工作要點》,推廣編程教育,并對 2 萬名中小學生信息素養評測。我們可以看到,全民編程時代即將來臨,愈來愈多的小程序員立志加入到 IT 隊伍中來:
“未來我想當個程序員,不怕掉頭發~”
“雖然我沒有獲得很多榮譽,也沒有那位小哥哥長得帥,但我的頭發更加濃密,具有更多的發展空間!”
“這(代碼)也太簡單了吧!”
在剛過去的暑假里,100 多位平均年齡只有 13 歲的“小小程序員”參加微信主辦的 2019 全國青少年微信小程序編程創意營,并自信地發出以上的宣言。
在首屆微信小程序編程創意營中,這些“小小程序員”們的創意無限,“編程力”十足:
我 11 歲,4 年編程經驗
創意營里年紀最小的是賴宥誠,別看他只有 11 歲,卻已是具有 4 年編程經驗的“老司機”。
他 7 歲時,接過爸爸丟過來的編程書,開始踏上編程之路。隨后他自學 Python、HTML 等語言,并用 Scratch 編寫一些小游戲和學習樂高機器人編程。在 2018 年的 WRO 比賽(國際奧林匹克機器人大賽),他所在的團隊獲得了季軍。
但對于微信小程序,賴宥誠還是從此次的創意營首次“觸電”,他零基礎學習微信小程序開發,不到一周便寫出自己的第一個小程序——LYC計算器小程序。
談到設計初衷,賴宥誠觀察到同學們做數學題時經常記不住計算公式,于是他想設計出一款方便計算的小程序來代替麻煩的人工計算。
這個計算機小程序包含有面積、體積、表面結合勾股定律、二元一次方程組、一元二次方程、求最大公因數和最小公倍數等囊括中小學的數學公式及方程,可謂是公式“百寶箱”。
LYC計算器小程序
在開發的過程中,賴宥誠也曾陷入思維慣性:在求最大公因數時,賴宥誠一開始想的算法是:找出兩個數的因數全找出來,再一個個比對。但他很快發現這是行不通的,在數據很大的時候這個算法很慢,因數太多了,最后他找到歐幾里德算法來解決。
他和其他程序員一樣,也有“不小心”刪掉一個模塊又重寫新代碼的痛苦經歷。
畢竟賴宥誠是單槍匹馬開發這小程序的,負責邏輯設計、UI、發布整個小程序的所有工作。
筆者現場發現,在創意營眾多的小小程序員中,賴宥誠年紀雖小,可表現沉穩、表達清晰、動手能力強,其他同學很自然地圍繞在他身邊觀看其編程操作。
在大家開始學習開發一款小游戲時,有些同學還在問老師,賴宥誠已打開編輯器,慢悠悠地開始動手設計了。
賴宥誠說自己很喜歡編程,平常學習任務重,他會在周末自發地學習編程。談及未來,他表示“未來我會繼續努力學習,成為一個厲害的程序員,設計很多優秀的軟件!”
碼二代、“歷史迷”,打造學習歷史小程序!
13 歲的張哲涵是一個“碼二代”:父母均從事編程工作,耳濡目染下,他在小學四五年級開始學習編程,有 C++ 編程背景。張哲涵還是個“歷史迷”,喜歡看《上下五千年》《明朝那些事》《中國通史》等歷史書籍。
從 2021 年開始歷史將列入廣州中考,歷史將成初中生們的“必修課”。傳統學習歷史的方法是枯燥地死記硬背,如何能幫忙同學們更靈活、有趣地學習歷史呢?
對編程和歷史均熱衷的張哲涵和其他兩位同學一同開發了一款學習歷史小程序——“知史乎”。
這個小程序中功能很豐富:設有 “中國古代朝代排序游戲”“教材知識點梳理”、“易錯題和難題答題游戲”、“答題方法分析”、“歷史笑話”等功能,集知識干貨與趣味學習于一體。
知史乎小程序
需要開發這么多的功能,他們在開發過程中遇到不少問題,處理方式也很“程序員”:
1、需學習新的編程語言,JavaScript、WXML、WXSS,他們參考微信開發文檔,邊做邊學。
2、在開發過程中,遇到“很多很多”Bug,耐心地一個個Debug。
3、在上傳歷史資料時,需要上傳的文字和圖片太多了,文件太大會導致上傳卡機。最終利用小程序后臺的分包功能,成功將初中歷史知識點、答題方法解析等眾多知識點全部上傳成功。
目前,“知史乎”小程序僅包括七年級的歷史,談及未來,張哲涵表示它“不是曇花一現的產品”,后續將會加入更多的歷史課程內容。
看到這,可能很多程序員會有疑問,“這些小程序員是如何做到的?”
三小時開發小程序,他為何直呼“太簡單”?
8 月 29 日,筆者在騰訊濱海大廈觀看小小程序員的一節編程課:在三個小時內開發一款美食地圖小程序。
在輔助代碼和輔導員的指導下,小小程序員們按照操作步驟完成環境部署、查詢、刷新、列表、查看、新增等功能體驗,并調試與運行。
有趣的是,有一位小同學邊調試程序邊直呼:“太簡單了!”
小程序員現場開發
可能會有人發出疑問:三個小時可能連服務都還沒部署好?為什么他們能這么快能開發出一個小程序?
為了讓大家專注前端業務功能,這次課程借助小程序·云開發來負責后端處理。云開發是由微信團隊和騰訊云聯合打造的 Serverless 云服務,是一個支持小程序、Web 等多端的應用開發平臺。提供云數據庫、云存儲、云函數、日志和監控等開發運維能力。開發者可使用云開發平臺,調用小程序的開放服務,來提升開發效率,快速試錯和落地產品。
云開發還提供一個便捷的技術是“免鑒權”。為了保護用戶數據安全,小程序開發者每次調用小程序開放平臺的能力前都需要微信鑒權獲取,而原有的鑒權流程較復雜,有些場景下還需要額外部署緩存服務,對開發者來說成本較高。
使用云開發后,只需要調用一個接口,就可以獲取用戶登錄態(OpenID),節省開發者的時間,提高效率。
小程序所見即所得,學生快速上手
在去年 11 月第五屆世界互聯網大會上,小程序獲選“世界互聯網領先科技成果”。馬化騰曾表示,小程序打破了過去受限的開發環境,構建出一個新的開發環境和開發者生態,為“跨系統開發”這個世界難題給出了中國的解決方案。
小程序簡單、易上手,這是微信從平均年齡只有 13 歲的初中生開展創意營的前提。
騰訊集團市場與公關部副總經理張軍表示, 過去我們的語言體系太復雜,沒有一個高層次的數學知識,非常好的邏輯體系,你可能都沒有辦法真正的編程,即使你學 C++,剛學的時候也只是淺顯地知道語言,能夠做模擬式的程序運營,但不能跟自己的真實生活聯系起來。
而小程序所見即所得,編完之后馬上被身邊的人使用,給中小學生的成就感很大。
張軍還表示:我希望小學生、初中生能在自己的正常學習之外能有一些興趣,如果這些興趣還能反哺到對學習,這是很好的促進模式。
所以我們希望創造這樣的環境,這里面有很多新的編程模式、編程體驗,都能讓他們體驗,這對他們來講是最大的樂趣。
在培養小程序員的編程能力時,為什么此次采用創意營的方式來讓學生學習小程序編程,而不是采用傳統方式,大家坐在課堂上,看著教材,聽老師講課?
廣大附中教育集團副理事長、廣州大學附屬中學副校長李衛表示:
因為(計算機)教材已經遠遠落后時代步伐了。
比如(小程序)編程,這樣的語言很好,由淺到深,(讓學生)先接觸,先入門,再慢慢深入,這是很好的(學習方式)。所以我們現在也提出信息課的改革,結合現在的需要開設課程。
從本次微信小程序編程創意營中,我們看到這些小小程序員對編程的熱愛與創意,相信在不久的將來,會為 IT 界注入年輕新力量。
與此同時,作為專業程序員的我們亦有危機感,不斷學習。因為時代不斷在變化,學習編程的門檻將越來越低,正如 AI 界的大牛吳恩達所說:
“現在人機交流正在變得越來越重要,可以預見,編程能力將會成為未來最深層次的人機交流的基礎。所以我不認同那些認為世界上只需要幾百萬程序員的觀點,在我看來,(幾乎)每個人都應該學習編程,就像每個人都應該學習閱讀和寫作一樣。”
【End】
初在研究對移動網絡傳輸進行功耗優化,在一次意外的監聽網絡傳輸包中截獲了微信小程序的請求包,借此來窺探當下前端代碼安全。
0x01 小程序分析
小程序包結構
Segment | Name | Length | Remark |
---|---|---|---|
Header | FirstMark | 1 byte | 0xBE 固定值 |
Edition | 4 bytes | 0 -> 微信分發到客戶端 1 -> 開發者上傳到微信后臺 | |
IndexInfoLength | 4 bytes | 索引段的長度 | |
BodyInfoLength | 4 bytes | 數據段的長度 | |
LastMark | 1 byte | 0xED 固定值 | |
FileCount | 4 bytes | 文件數量 | |
Index | NameLength | 4 bytes | 文件名長度 |
Name | NameLength bytes | 文件名,長度為NameLength | |
FileOffset | 4 bytes | 文件在數據段位置 | |
FileSize | 4 bytes | 文件大小 | |
LOOP...... | |||
Data | Files Package...... |
包結構非常清晰,分為三個部分:
頭信息,包含一些包的標識,版本定義等,包含了三個冗余字段 --- 索引段和數據段的長度應該是用于做校驗,但實質上沒有用(設計者覺得需要設計一些冗余字段來確保設計的完整性,防止解析的時候溢出,但實際工程實踐中并沒有起到相應的作用),文件數量應該是用于簡化包解析過程,實際上知道了索引段長度或數據段長度中任何一個皆可推算出文件數量。
索引段,包含文件的元信息 --- 文件名以及文件位置(通過FileOffset和FileSize定位數據段中的文件)。如果從精簡包的大小的角度來看,FileOffset和FileSize只需一個存在即可,但是這樣解析包的難度就大大增加了,還是以工程實踐為主。
數據段,將所有的文件羅列在一起。
由此可見,數據完全沒有經過壓縮或者加密,連包的簽名信息也沒有。這導致只能在制品流程上進行嚴格控制,例如在開發者上傳代碼過程中需要授信,必須經過審查,也一定得由授信平臺進行代碼分發等。這些都無關風月,畢竟App Store就是這種模式,但是......
如何拆解這種自定義文件格式呢?
對多個相同格式的文件進行對比,對大體結構有宏觀的感覺,很容易發現一些固定的字段以及一些結構的長度。對于像小程序這種有軟件本體的例子,還可以通過微量修改來觀察文件的變化來找到文件結構和意義。
觀察特殊形式,首先英文的字符串是很明顯的,一般hex編輯器都自帶字符串化窗口,如果發現常見的字符串,就可以繼續去尋找字符串的邊界,字符串在二進制文件里有兩種儲存方式:一種是不記錄字符串的長度,讀取字符串到0x00位置,另一種一定在某一個地方儲存了這個字符串的長度,因此一旦得知了該字符串的內容,搜索該長度字段即可獲取更多的信息。其次一些文件頭也非常顯眼,例如PNG[1]、ZIP[2]等通用標準文件格式都有固定的文件頭,在小程序的自定義格式中很容易發現一些png、jpg等資源的文件頭,因此可以定位數據區的位置。
對特定區域的二進制進行推理猜測,一般來說二進制文件里需要儲存大量的offset和size的數據作為數據段的索引。offset相當于一個指針 - 索引文件在數據段的位置,工程實踐中,大部分儲存了offset的地方也會儲存size字段,畢竟在解析文件的時候會方便很多,也可以防止校驗數據出現指針越界。因此,一旦確認了文件中的數據段,就可以通過它的位置(offset)和大小(size)的實際數據進行搜索,逆向找到指向它的數據位置,并且繼續逆向直到解析完整的文件。另外,如果要考慮設計的完備性,需要在二進制文件中加入一些冗余字段進行校驗或者糾錯,例如CheckSum、CRC32、Alder32、MD5、ECC等,這些通過hex編輯器很容易計算并發現。小程序中FileCount的字段,這完全是為了工程實踐考慮的,在小程序中并沒有出現這類的計算值,這是可能是因為小程序為了簡單設計考慮,一旦發現包體被篡改或損壞就直接丟棄。
其實拆解小程序這種格式并不需要花費特別多的精力,因為其格式比較簡單,而且從下圖流程上來說,后綴為wx的二進制格式很可能與wxapkg格式是同源的。
開發者工具上傳服務器分發原始代碼后綴為wx代碼包處理為wxapkg格式包體客戶端
從開發者工具的代碼中的pack.js很容易發現一些對wx格式封裝的痕跡,只不過其中unpack.js的代碼被隱去了。通過實際的分析發現(wxapkg文件可以通過截獲網絡包請求獲得或者在本地的微信appbrand目錄下可以發現),wxapkg格式就是將wx格式進行了轉化:Wxml -> Html、 Wxml -> JS、Wxss -> Css,其二進制格式跟后綴名為wx二進制格式完全一致。我寫了兩個版本的解析二進制包的代碼(Javascript版本傳送門,python版本傳送門),其實非常簡單,根據小程序包結構一步一步解析,基本上沒啥難度。但如果要將Html -> Wxml, JS -> Wxml, Css -> Wxss進行還原,其中JS -> Wxml的過程中需要將if語句轉變成wx-if標簽、for語句轉化成wx-for標簽有點麻煩,需要對解析包后的page-frame.html中 JS 代碼進行修改,修改細節太多就不再詳說了,總之微信小程序的代碼沒有經過額外的保護措施,比較容易進行還原。
(PS:暴露一下微信小程序未公開的API,openUrl- 在小程序中打開外部網頁;getGroupInfo- 獲得群的名稱,群內成員昵稱等數據;getOpenidToken - 獲得用戶openid;這些權限微信應該是沒有準備開放的。每次在進入小程序時,客戶端都需要先去請求該小程序的元數據,例如應用名、版本號、一些權限列表、代碼包下載地址等描述信息,修改這些元數據可以獲得相應的權限,小程序的關鍵信息完全由后臺控制進行配置,另外小程序的本地文件存儲采用HASH映射機制進行文件定位,文件存儲在外部存儲,本身通過自定義算法實現完整性校驗 - 首先,小程序最終存儲的文件名是:對稱加密(文件流內容Alder32校驗和 | 原始文件名)生成的,最終文件名和文件內容會通過自校驗判斷完整性;其次,本地緩存是通過HASH映射查找文件。所以即使能破解文件名和文件內容,繞過文件自身簽名校驗,篡改為攻擊者的偽造文件,小程序APP也無法映射到該偽造文件進行使用。)
由上可見,微信并沒有在代碼安全上進行過多的考慮。這導致需要在應用審核過程中花費比較多的功夫,不然作品太容易被復制竄改,以至于會失去渠道先機,這對流量是致命打擊。由于歷史原因,前端的代碼安全技術發展的比較緩慢,相比其他被編譯成二進制的應用,前端這種純文本應用,太容易被辨識與竄改。
對前端代碼進行保護的目的在于讓機器容易識別相關的指令,而使人難以理解代碼的邏輯,但往往在對前端代碼進行保護過程中,很難既兼顧指令效率又能使可讀性降低。因此,常常需要在現有的代碼中增加一些額外的驗證邏輯,例如一些增加無效的代碼進行混淆、采用守護代碼保護業務代碼不能在其他的域名下正常運行、增加一些防止調試跟蹤的斷點等,這些措施都是使得破解代碼時人工成本增加,從而增加代碼的安全性。
下面提供一些能夠增加前端代碼安全性的策略:
1. 精簡(minify)
這是最簡單且無害的方法,精簡代碼能減少代碼體積,從而減小數據傳輸的負荷,同時也能降低代碼的可讀性。在小程序開發者工具中也提供該選項。對Javascript代碼進行精簡大致可以從以下幾個方面入手:
刪除注釋,刪除無意義或者多余的空白,刪除可以省略的符號
刪除一些沒有調用的代碼(Dead code),對函數進行精簡(三元運算符?:、字符串操作、對象函數、對象繼承、函數引用、無名函數、遞歸函數)
將變量名進行簡化,將零散的變量聲明合并,縮短語句
......
常用的工具有很多:YUI Compressor、UglifyJS、Google Closure Compiler、JS Packer、JS Min...
使用工具對代碼進行精簡時需要注意:1. 最好備份原始代碼,方便調試與后期修改。 2. 用于調試精簡代碼時保存的sourcemap,在線上應該刪除。 3.編寫代碼的時候應該嚴格按照規范,最好使用lint工具對代碼進行檢查,精簡代碼后導致代碼不可用時,調試非常困難。
這種簡單的方法雖然很實用,但是也很容易被還原出源代碼,使用一些代碼格式化工具可以補齊被刪除的空格、換行、符號等,例如jsbeautifier。另外2015年就有相關的研究,從大量的代碼中推測出被精簡的代碼,因為人寫代碼總有固定的范式,所寫的代碼相似性都非常的高,如果用統計方式就很容易反推源代碼,蘇黎世聯邦理工大學Martin Vechev教授領帶下開發的工具JSNice就是一款運用條件隨機場(Conditional Random Fields)機器學習和程序分析方法來還原Javascript代碼利器,利用大量的開源代碼,去學習命名和類型的規律。JSNice可以用于以下不同的方面:反精簡的JavaScript代碼、對當前的代碼提供更多的更有意義的變量名、自動化程序的注釋等。相關論文傳送門 后臺代碼傳送門
2. 混淆(obfuscation)
混淆可以減低代碼的可讀性,防止被輕易追蹤出程序邏輯。常見的混淆方法有如下幾種:
通過編碼混淆代碼,這篇文章《Javascript常用混淆方法》里面介紹了很多不錯的編碼加密方法。但是這些方法有個明顯缺點,增加代碼體積,而且編碼加密都是可逆的。
將標識符混淆和控制邏輯混淆(分離靜態資源、打亂控制流、增加無義的代碼等),例如aaencode和jjencode。
標識符混淆的方法有多種,有些與編碼混淆代碼方法有些重疊,常用方法有哈希函數命名、標識符交換和重載歸納等。哈希函數命名是簡單地將原來標識符的字符串替換成該字符串的哈希值,這樣標識符的字符串就與軟件代碼不相關了;標識符交換是指先收集軟件代碼中所有的標識符字符串,然后再隨機地分配給不同的標識符,該方法不易被攻擊者察覺;重載歸納是指利用高級編程語言命名規則中的一些特點,例如在不同的命名空間中變量名可以相同,使代碼中不同的標識符盡量使用相同的字符串,增加攻擊者對軟件源代碼的理解難度。
控制混淆是改變程序的執行流程,從而打斷逆向分析人員的跟蹤思路,達到保護軟件的目的。一般采用的技術有插入指令、偽裝條件語句、斷點等。偽裝條件語句是當程序順序執行從A到B,混淆后在A和B之間加入條件判斷,使A執行完后輸出TRUE或FALSE,但不論怎么輸出,B一定會執行。控制混淆采用比較多的還有模糊謂詞、內嵌外聯、打破順序等方法。模糊謂詞是利用消息不對稱的原理,在加入模糊謂詞時其值對混淆者是已知的,而對反混淆者卻很難推知。所以加入后將干擾反匯編者對值的分析。模糊謂詞的使用一般是插入一些死的或不相關的代碼(bogus code),或者是插入在循環或分支語句中,打斷程序執行流程。內嵌(in-line)是將一小段程序嵌入到被調用的每一個程序點,外聯(out-line)是將沒有任何邏輯聯系的一段代碼抽象成一段可被多次調用的程序。打破順序是指打破程序的局部相關性。由于程序員往往傾向于把相關代碼放在一起,通過打破順序改變程序空間結構,將加大破解者的思維跳躍[3]。
另外還有些混淆方式是專門針對于反混淆工具設計的,這就需要去仔細分析反混淆工具的原理,在一些特定的地方插入代碼使反混淆器進入死循環或者異常跳出。
一般來說,提供代碼精簡的工具都會提供一些混淆的方法,除此之外,比較知名的商業工具有jasob、jscrambler,一般越商業的越難被反混淆,然而這些高級的代碼混淆也常會被用于隱藏應用中的惡意代碼。對惡意代碼進行混淆是為了躲避殺毒軟件的檢測,這些代碼在被混淆擴充后會難以被識別為惡意軟件。相應的也有一些反混淆的工具出現,例如上面提到的JSNice工具能夠對混淆的代碼進行推理,另外反混淆工具JSDetox專門針對一些混淆方法做過專門的支持。反混淆一直是一項體力活,根據不同的混淆策略需要進行反推演算,這就是一場攻與防的游戲罷了。
3. 加密(encryption)
加密的關鍵思想在于將需要執行的代碼進行一次編碼,在執行的時候還原出瀏覽器可執行的合法的腳本,在某個角度也可以認為是一種混淆的形式,看上去和可執行文件的加殼有點類似。Javascript提供了將字符串當做代碼執行(evaluate)的能力,可以通過 Function constructor、eval、setTimeout、setInterval、Worker、DOM event等將字符串傳遞給JS引擎進行解析執行,由于有些需要用到eval函數,會導致代碼性能會減低。以Worker執行舉例:
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編碼,一種簡單的方法就是將代碼轉化成base64編碼,然后通過atob以及eval進行解碼然后運行,另外一種采用base62編碼技術更為常見,其最明顯的特征是生成的代碼以eval(function(p,a,c,k,e,r))開頭。無論代碼如何進行變形,其最終都要調用一次eval等函數。解密的方法不需要對其算法做任何分析,只需要簡單地找到這個最終的調用,改為console.log或者其他方式,將程序解碼后的結果按照字符串輸出即可。
(PS: 從算法上看,packer是一種base64編碼字典壓縮策略,packer的base64編碼的壓縮率很高,精簡后代碼依然可以減少50%體積以上,因為帶有解壓器和字符表,越長的代碼理論上壓縮率更高,想要了解詳情可以看看這篇文章《Packer,你對我的JS做了什么!》)
使用復雜化表達式,在Javascript中可以把原本簡單的字面量(Literal)、成員訪問(MemberExpression)、函數調用(CallExpression)等代碼片段變得難以閱讀。例如這個方法僅用+!等符號就足以實現幾乎任意Javascript代碼。在 JS 代碼中可以找到許多這樣互逆的運算,通過使用隨機生成的方式將其組合使用,可以把簡單的表達式無限復雜化。
隱寫術,將 JS 代碼隱藏到了特定的介質當中。如通過最低有效位(LSB)算法嵌入到圖片的 RGB 通道、隱藏在圖片 EXIF 元數據、隱藏在 HTML 空白字符、放到css文件中(利用content樣式能存放字符串的特性)等。比如一張圖片黑掉你:在圖片中嵌入惡意程序,這個正是使用了最低有效位平面算法,結合HTML5的canvas或者處理二進制數據的TypeArray,抽取出載體中隱藏的數據(如代碼)。隱寫的方式同樣需要解碼程序和動態執行,所以破解的方式和前者相同,在瀏覽器上下文中劫持替換關鍵函數調用的行為,改為文本輸出即可得到載體中隱藏的代碼[4]。
混合加密,單個方法容易被破解,但組合起來就不會那么容易了,破解成本也會指數增長,例如jdists采用組合加密和嵌套加密的方式。
這些加密的方式都很容易通過對源代碼進行詞法分析、語法分析進行還原,首先將代碼的字符串轉換為抽象語法樹(Abstract Syntax Tree, AST)的數據形式,然后從語法樹的根節點開始,使用深度優先遍歷整棵樹的所有節點,根據節點上分析出來的指令逐個執行,直到腳本結束返回結果。這種方法大多數用于對代碼進行優化,例如最近Facebook開源了代碼優化工具Prepack,可以自動消除冗余代碼,降低打包體積和執行時間,基本上就可以用來將這些加密的字符串進行還原,畢竟編碼這些字符串都是可以通過詞法語法推測出來的。
4. 編譯(compile)
Github上有一份清單記錄了所有Javascript擴展語言,這些語言都可以通過編譯器轉化為Javascript語言,這也是前端發展的一個趨勢,原來寫的html,css,Javascript已經開始變成了一個“中間語言”,而且越來越多的團隊也有了自己的一套前端編譯系統。Javascript越來越像Web中的匯編語言,特別是近些年Node的普及,讓前端變得越來越復雜,大量前端框架的出現,使得Javascript代碼可以通過手工編寫,也可以從另一種語言編譯而來,詳情參考幾年前Brendan Eich(JavaScript之父)、Douglas Crockford(JSON之父),還有Mike Shaver(Mozilla技術副總裁)的郵件。通過編譯后的Javascript代碼越方便機器的理解,降低可讀性,在某一定角度上講,這也不愧為一種代碼保護措施。據說幾大科技巨頭正在醞釀給瀏覽器應用設計一款通用的字節碼標準——WebAssembly,一旦這個設想得以實現,代碼保護將可以引入真正意義上的“加殼”或者虛擬機保護,對抗技術又將提升到一個新的臺階。目前在桌面端,使用NW.js框架可以JavaScript應用程序的源代碼可以被編譯為本地代碼,在運行時通過NW.js動態還原出源代碼,但是這種方法目前會比正常的JS代碼慢30%左右。
5. 防止被調試
對代碼進行破解分析無非分為靜態分析和動態分析,如果對代碼進行混淆加密等形式操作,那么靜態分析就很麻煩了,對代碼調試跟蹤分析可以對代碼整體邏輯有一個宏觀的把控。例如首先判斷瀏覽器是否開啟了開發者工具控制臺(目前最完美的解決方案傳送門),如果檢測出控制臺開啟則堵塞Javascript執行或讓代碼異常跳出。另外Android 4.4及以上和iOS是支持webkit remote debug的,因此應該在debug模式下,設置代碼可以被debug,release模式下,禁止debug以保護數據安全。
6. 前后端協作
首先得強調的事情是不要在前端放敏感數據,前端容易破解,因此需要配合后端進行安全防護,例如微信小程序的登錄,必須利用授信的后端配合才能完成此項功能,另外在小程序的網絡請求中的referer是不可以設置的,格式固定為https://servicewechat.com/{appid}/{version}/page-frame.html,其中{appid}為小程序的appid,通過驗證appid字段可以抵御一些直接的山寨,其次就是加快迭代速度更改代碼保護策略,這樣可以讓之前的分析失效,增加破解的成本。
以上就是對當前前端代碼安全進行的探索,最后用一句話結束:
Beneath this mask, there is more than flesh. Beneath this mask, there is an idea. And ideas are bulletproof.
作者:不詳
出處:知識商店
*請認真填寫需求信息,我們會在24小時內與您取得聯系。