Typora解密之跳動的二進制
磨刀霍霍 :準備工作
開發環境識別
用IDA打開 Typora.exe (不建議,可能是我電腦問題,IDA分析了三四個小時),打開后發現程序中有 electron ,V8字樣,由于前幾天剛好分析了一個IE漏洞,由V8聯想到 JavaScript引擎,此時猜測與JS語言有關。


打開程序目錄,查看一下有沒有JS代碼,一番查找,看到了asar文件以及node字樣,感覺有些眼熟哪里見過。問一下度娘,百度搜索asar,electron,nodemodules
不搜不知道,一搜嚇一跳,原來這是使用 NodeJs的electron框架開發的桌面應用,JS也能寫桌面程序了。
繼續搜索相關資料,得到以下信息:
1.electron 使用了谷歌的 V8引擎以及渲染引擎(意思是這種程序跟個瀏覽器差不多唄)
2.electron 有主進程與渲染進程 ,通過IPC交換信息,渲染進程只負責渲染(難怪我附加調試有好幾個進程。。)
3.Typora.exe是electron框架,基本與開發者的代碼無關(修改框架復雜度太高,一般人不會去動,所以別去逆向它了)
4.查看目錄發現,在app.asar.unpacked中發現了一個 main.node ,看名字很奇怪,.node文件是啥東西
5.app.asar是打包的JS代碼,并且只是簡單的打包,沒有任何加密措施,且electron也沒有代碼保護措施(圈起來要考)
根據第五條信息,筆者嘗試使用工具對asar進行解包,解包失敗不知道什么原因(或許必須使用NodeJS自帶的解包工具?但是解包也是加密文件,我又懶得下載nodejs,需要的時候再解吧),放進010editor查看,得到文件名與一些密文,此時陷入了僵局。

嘗試調試
打開typora進程,x64dbg附加,查看有沒有有用的信息(剛開始我不知道這個是NodeJS開發,想著通過注冊窗口跟蹤程序流程,好家伙差點就逆到引擎去了),發現了有好幾個進程,后面命令行參數可以看到渲染gpu等關鍵字,并且目錄參數指向了app.asar。

根據前面得到的信息,這些進程都是由主進程創建的,程序邏輯是由主進程處理,那么附加主進程看一下(沒參數的那個)

看到了主進程加載了main.node模塊,什么.node也能當dll加載 ? 或許它本來就是一個dll呢(也不排除加殼后,手動加載的情況)
那么使用PE工具查一下,VS2017編譯的64位DLL。
二
梅開二度 :邏輯分析
到這一步,已知信息:
1.JS文件被加密
2.框架為electron
3.框架會加載main.node模塊
4.解析JS腳本的是V8引擎
5.c++支持node api進行開發
根據我們的已知信息,來對程序的整體邏輯進行一個簡單的分析。
1.框架對代碼無保護且修改框架難度過高,V8引擎不支持解析加密的JS代碼,那么JS代碼如何運行?
站在一個開發者的角度,我可能會想到由框架加載我的解密代碼,把js代碼解密后送到js引擎去執行就可以了
2.那么解密代碼放在哪里合適呢?
既然框架被編譯為二進制了,且根據之前的分析,妥妥的c++開發,想要最簡單的方法實現解密,加載同樣為c/c++編譯的二進制代碼即可。在Windows平臺上,想要加載代碼執行,也就剩動態鏈接庫了。由此可以推測,之前找到的main.node,可能就是解密模塊。
邏輯總結
- 框架加載解密模塊
- 解密模塊對app.asar進行解密(解密后會不會把文件寫出來呢,如果寫出來可以直接拷走。。)
- 解密后的代碼送入JS引擎執行
- 另外的邏輯 :由解密模塊解密app.asar的xxx.js代碼,xxx.js代碼執行后,由它來負責解密剩下的js代碼并執行,可以提高破解難度三
精益求精 :main.node分析
1.根據之前的推測,main.node負責解密,按照程序員開發習慣,Ctrl CV實現,也就是使用公開的一些算法
2.IDA加載main.node,按照逆向慣例,先搜索一波字符串
此時看到了buffer,base64,app.asar等關鍵字。猜測一下,app.asar加載到buffer然后進行base64解密。

3.繼續開展搜索工作,base64未免太過簡單了吧,使用FindCrypt3插件 ,搜索一下算法常量吧。
此時找到了AES的算法常量,前兩個是重復的,可能是插件問題。

4.好的,現在面臨一個問題,我不懂算法,怎么解密。。。只能去問度娘了,搜索一下AES加密解密原理與 C 實現代碼。
5.根據搜索得知:
- AES使用最后那個常量數組進行解密
- AES有五種加密模式,常用的為ECB CBC模式
- 根據AES密鑰長度,有不同的加密輪數(不是很懂哈。。)
6.對解密常量進行交叉引用跟蹤,找到這個函數以后,繼續對函數進行交叉引用跟蹤。

大概經過三四次的跟蹤,發現了這個函數,在這個函數里F5查看反編譯代碼,發現了app.asar字符串的引用。


7.此時進行推測,這個函數加載了app.asar的內容,并且調用 SUB_180003E40進行解密。
8.跟進SUB_180003E40 進行查看 ,此時發現了base64字符串的引用,推測對buffer進行了base64解密。

9.看到很多不認識的API,百度搜索得知,這是Node API,簡單去看一下函數功能http://nodejs.cn/api/n-api.html#napi_call_function
NAPI_EXTERN napi_status napi_call_function(napi_env env, //環境 napi_value recv, //名為global的值 napi_value func, //要調用的javascript函數 size_t argc, //JavaScript函數的參數個數 類似argc const napi_value* argv, //JavaScript函數的參數數組 類似argv napi_value* result); //返回的JavaScript對象
10.根據文檔得到的信息,參照這一部分Node API,得到了如下信息(猜測調用了此函數)筆者對這語法難以理解,程序的目的是把對象進行base64編碼?但是看這代碼,base64也沒有被當作參數傳遞進去
Buffer.from( object, encoding )object:此參數可以包含字符串,緩沖區,數組或arrayBuffer。encoding:如果對象是字符串,則用于指定其編碼。它是可選參數。其默認值為utf8。 Buffer.from(string[, encoding]):返回一個被 string 的值初始化的新的 Buffer 實例
11.暫時先不管node api的語法與功能,繼續往下看。

看到了這部分的函數調用,進入查看,發現與 C實現AES算法結構相似,推測這部分為AES解密。
分析總結
根據目前的分析,得到如下信息
- main.node模塊使用node api 進行js函數調用
- main.node模塊使用了AES解密算法(模式未知)
到了這一步,想要繼續破解,首先要得到js代碼,有兩個辦法以及面臨的問題。
1.分析算法,找到密鑰,如果是CBC模式,還需要找到IV ,之后使用解密算法,解密app.asar的js代碼
2.分析程序執行流程,找到解密后的緩沖區,直接拷走,得到徹底解密后的js代碼。
面臨的問題:
- nodejs語法不懂,這些api調用的具體js函數不清晰
- AES解密流程不熟悉,找到密鑰或者iv的難度較高(通過加密輪數判斷密鑰長度,通過算法部分判斷加密模式,并找到相關數據)
- 就算你找到解密AES的辦法,它會不會還有別的加密措施與防護措施,例如密鑰需經過hash摘要,文件完整性校驗等(如果還有加密保護措施,那還得繼續分析別的算法,一步一步還原,對于算法不熟悉的人時間成本太大了)四
大海撈針 :尋找JS代碼
1.根據之前的分析,我選擇第二種獲得js代碼的辦法,分析程序執行流程得到解密后的 JS代碼。
2.分析解密前的 js函數調用,由之前的分析得知,參數有兩個,在V27的位置,V27是由參數 a3 +8 得來的,動態調式一波。

3.x64dbg打開typora.exe,下一個dll斷點,根據之前分析,他是動態加載的(多打了個a 懶得換圖了)

4.斷點設置完成后,斷在loadlibrary,進入模塊入口后,去IDA計算偏移,定位到前面分析的函數位置。
對了,記得使用x64dbg自帶的 PEB隱藏功能,并忽略所有異常,這個模塊有簡單的反調試手段(查看導入表可知,太過簡單不分析反調試手段了)

定位函數位置,直接看后4位即可,同樣為模塊偏移 674A的位置。


5.下斷后查看decrypt(命名一下方便)函數的參數,x64架構下,函數參數為 rcx rdx r8 r9 rsp+0x20
前四個從左到右,超過四個則入棧,rsp+0x20為起始地址,詳情可參考微軟x64調用約定。
6.r8 = a3 查看 *(a3 + 8)的值, ,這個值為之前分析的 v27 地址,也就是argv 繼續查看指針指向內容。


現在得到了buffer.from js函數的兩個參數 ,第一個像是密文,第二個沒看出來,不是預料中的base64,所以之前的推斷貌似是錯的?
7.關注一下這兩個地址 0000079908482119 00000799083CFEA5 在調用完js函數會有什么改變,直接來到調用的位置,調試器同步來到這個位置。


來到這個位置再次確認參數,第五個參數為 rsp+20,也就是rax的值,rax為argv,進入內存查看得到前面同樣的地址,也就是 (a3 + 8),步過這個函數,查看對參數的改變。
8.執行完后查看剛才記錄的位置,好的好的,耍我呢,啥都沒變,并且后續也沒調用相關數據(或許是最后一個參數返回了一個對象,忘記看了,如果返回的話應該是密文相關的東西,并且放到了某個數據結構中,所以在IDA中沒看到直接使用的行為)
繼續分析,到了AES解密代碼部分,既然是解密,那肯定得把密文的緩沖區拿過來吧。

首先看到一串16進制的賦值,v46開頭的數組 剛好32個字節,也就是256bit有點像是AES-256的樣子了。然后看到申請了 32字節的內存,v32
之后調用了 sub_18000B060函數,對v46 與 v32進行操作, 目測參數為 (目標地址,源地址,大小)
進入 sub_18000B060函數查看,一大堆運算,根本不想看,根據之前的推測,可能是作者感覺直接把密鑰放在程序中有些不妥,所以對密鑰進行一個類似于解密或hash運算的工作(不展開分析了)
9.繼續分析

可以看到sub_180007000 函數 ,參數v45 IDA提示我是一個 char[256]的數組,v32為32字節的地址,v10為一串神秘數據。
可以得到一個結論,在經過 v46的一系列運算,得到了一個同樣大小 32字節的數據,再把數據 與 v10神秘數據進行操作,放入v45的256字節的數組中(好家伙 這是密鑰嗎 搞這么復雜)
跟進sub_180007000 查看, 把v10放到了 v45數組的0xF0的位置
之后調用了sub_180007800函數對自己的PE文件有些操作,簡單看了下前面的匯編,主要內容為,把v32 放到 v45中,大小為32字節

10.繼續分析

接下來看一下sub_180005c00 函數,使用了v27,之前分析出來的密文地址就在v27中,v30沒看出來,應該是傳出參數后面用到了。
猜測這個函數對密文進行一波操作,看一下返回值用來做什么,這個偽代碼看的頭疼,匯編看一下。
.text:0000000180004021 call sub_180005C00.text:0000000180004026 mov rbx, rax.text:0000000180004029 mov r14, [rax+8] ;返回值+8的內容給 r14.text:000000018000402D sub r14, [rax] ; r14 - 返回值的內容.text:0000000180004030 mov rcx, r14 ; 得到一個大小 Size
人工反編譯一下 首先確定rax為一個指針 *(rax+8) - *rax 就是這個地址里面存儲了兩個值,拿第二個值減第一個值得到一個size。
此時猜測,這兩個值或許是 密文的開始地址與結束地址?
然后用這個size 申請了一塊內存 , IDA 命名為 Block , sub_18000B060 之前分析過, 對*v12進行操作, 結果給到Block
把V12代入rax中 , *(v12+8) - *v12 , 結束地址減去開始地址得到 size
由此可以驗證猜測, *v12 為密文開始地址 ,V13為密文大小
11.繼續分析第三個方框的內容
以v13 + 1 的大小 申請了一塊內存 v14 , sub_18000B060 對 Block 再次進行操作, 結果給到v14。
v14的最后一個字節置為0 ,推測已經把密文轉換為字符串了 , 需要一個 NULL 結尾。
;v15 = r8d rcx = v13 rbx = v14 .text:0000000180004094 movsxd rcx, r14d.text:0000000180004097 movzx r8d, byte ptr [rcx+rbx-1]
由上可得 v15 = v14[v13-1] , 也就是從v14中取了一個字節的值 ,位置在null字符的前一byte。
12.繼續分析sub_180006AC0

可以看到,sub_180006AC0 的參數 , v45(256字節數組),block ,v13。
結合之前對 sub_180007000的分析,可以得知, 目前v45的狀態 ,v45[0-31]為32字節的類似密鑰的東西 v45[0xF0] 為 v10的神秘數據 ,v13為Block大小。
跟進簡單查看 ,查看后感覺可讀性不好,筆者對照匯編代碼,重新修改了一下反編譯代碼。

__int64 __fastcall sub_180006AC0(v45,block,block_size){ if ( block_size ) { v3 = block; v5 = v45 + 0xF0 - (_QWORD)block; //v45+0xF0的地址 減去 block的地址得到v5 v6 = ((block_size - 1) >> 4) + 1; //做為外圈循環的次數 do { v7 = *v3; //v7為 xmmword 16字節浮點寄存器 ,把block的內容取16字節給v7 16字節符合AES塊大小 //由此推測block是真正的密文,將在這個函數中進行解密操作 sub_180007320(v3, v45); //用到了AES解密常量 應該是解密相關 并且對推測的key 也就是前32字節有一些操作 v8 = 16i64; //內圈循環16次 do { result = *((char*)(v3 + v5)); //block地址 + v5偏移 取一個字節內容 *(char*)v3 ^= result; //取block的1字節數據,與block地址 + v5偏移 進行異或 v3 = (__int128 *)((char *)v3 + 1); //block += 1 --v8; //總共16次 也就是16個字節異或 } while ( v8 ); v5 -= 16i64; //外圈循環 v5 每次-16 也就是每次異或 異或的值都會變化 范圍為-16字節 v45 + 0xF0 = v7; //block的16字節內容 給到v45+0xF0 --v6; //外圈循環次數 } while ( v6 ); } return result; }
根據目前的分析,可以推測 ,sub_180006AC0函數為 主要的解密算法函數,看著像是 AES CBC模式,因為對算法不熟悉,大膽猜測一下。
key存放在v45中, 前32字節 ,也就是256位 , iv存放在 block+v5中 (不清楚對不對)
13.繼續分析剩下的內容

好的好的,看著有點頭疼,后面的代碼大概意思就是,又對解密后的數據進行了一系列操作,最后返回了一個緩沖區。
讀者感興趣可以自行分析,實在是寫不動了。
釜底抽薪 :得到JS代碼
1.根據前面的分析,我們已經大致了解了程序流程,來到調用解密函數的函數,只需要在徹底解密后,送到JS引擎執行的時候,拿到解密的JS代碼即可。

2.根據上層調用代碼,可以得到,解密后返回了一個值,作為調用JS函數的參數 ,定位到678F偏移處,x64dbg同步定位。

3.斷下后查看v28的內容 , RSP+20 的位置,然后繼續查看這個指針的指針的內容,最后得到了解密后unicode形式的JS代碼。

4.把內容拷走,拿到010editor,把00去掉,變成ascii形式,檢查一下得到的數據。

看起來跟密鑰有關的一串字符編碼數據:

搜索一下license相關的數據, 找到不少,看起來也像是代碼,應該沒問題
指鹿為馬 :破解可行性分析
修改文件破解
如果懂算法與NodeJS,可以通過分析,找到關鍵的key等數據,對app.asar進行解包解密操作得到JS代碼進行修改后,打包回去即可
可能遇到的問題:對app.asar進行完整性校驗。
內存破解
簡單說幾種思路,由于main.node是后加載的模塊,所以內存破解有些難度。
- 調試器加載 :參照上述手段,在模塊加載通知中斷下,定位到解密函數下斷,修改內存中的JS代碼
- 導出表HOOK:參考病毒木馬使用的進程替換(傀儡進程)技術,創建進程后掛起,由于main.node中的node api是使用框架中的導出api,所以可以替換導出函數為自己的函數,在調用時進行參數判斷,如果為JS代碼,則修改
- DLL劫持:替換main.node,由自己加載真正的main.node并調用,調用時,定位到解密函數并hook,等待JS代碼并修改
- PE代碼注入 :修改框架的PE文件,并加載自己的DLL,加載后進行導出表hook
可能遇到的問題:對main.node或者框架進行完整性校驗,更加強大的反調試手段。
方法還有很多,不再一一列舉,這里只能提出思路
點到為止 :總結
- 通過這次逆向分析,踩了不少坑,學到了不少東西,并且加深了逆向技術的基礎。
- 作為一個逆向練習生,遇到不懂的,不會的,應該迎難而上,揚長避短,不可輕言放棄。
- 遇到一個糾結的地方,不要過度停留,逆向分析應該是分析大方向,站在開發者角度,根據分析出來的功能猜測作者的意圖,以找到關鍵突破點。
結語
- 由于筆者對算法與Node Js開發并不熟悉,所以沒辦法得到密鑰與其它解密數據(文章中關于算法的一些操作皆為推測,相信熟悉算法的大佬可以看出來密鑰所在)
- 對于最后得到的JS代碼也沒辦法判斷到底完不完整,是否還有未解密的部分,所以只能到此為止了(看起來是完整了)
- 經過筆者一段時間的努力,已經成功實現內存破解,詳情見下篇。