、Windows 11 正式版終于發(fā)布,最低配置要求來了
10 月 5 日,微軟開始正式推送 Windows 11。
Windows 11 相比 Windows 10 最大的變化,就是其全新UI界面,原先用戶最熟悉的開始菜單被重新設計—從左側轉移到中間。而 Windows 11 正式版開始使用新的開始菜單和任務欄,從新系統(tǒng)的每一個聲音、字體和圖標,微軟開發(fā)團隊都深思熟慮地考慮了每一個像素和細節(jié),希望為用戶帶來更現代、更新鮮、更美好的體驗。
通過全新的開始菜單,用戶可以快速訪問自己關心的內容和應用程序,而且“開始”菜單利用云和 Microsoft 365 的強大功能,無論哪個平臺或者設備上(即便是在 Android 或 iOS 設備),用戶都可以在“開始”菜單上查看到最近文件。
根據微軟官方公布的最新 Windows 11 最低硬件要求顯示,升級 Windows 11 依舊需要 TPM 2.0 以及符合要求的芯片處理器,因此很多想體驗 Windows 11 系統(tǒng)的用戶被此難住了。
對于硬件不支持升級 Windows 11 的用戶而言,微軟官方表示,對 Windows 10 的支持將持續(xù)到 2025 年 10 月 14 日,在此之前用戶還是可以購買和安裝 Windows 10。
1、Linux Lab 發(fā)布 v0.8-rc3,正式支持 Rust, LLVM 和 openEuler
Linux Lab 發(fā)布 v0.8-rc3,v0.8-rc3 重要變更如下:
2、Apache Camel 3.12.0 發(fā)布
Apache Camel 是一個開源的面向消息的中間件框架,Apache Camel 3.12.0 更新內容如下:
3、LLVM 13.0.0 正式發(fā)布
LLVM 13.0.0 現已發(fā)布。本次更新內容主要包括 bug 修復和一些小型功能優(yōu)化:
4、福布斯發(fā)布美國富豪榜:貝索斯、馬斯克、扎克伯格前三
昨日,福布斯發(fā)布了最新美國富豪榜,前三名都是美國科技公司的創(chuàng)始人,分別為:杰夫?貝索斯、埃隆?馬斯克、馬克?扎克伯格。
數據顯示,在前 20 位最富有的美國人中,有 8 個人的身價超過了 1000 億美元,與此相比,該人數一年前只有 2 個,2017 年則是 1 個都沒有。此外,本次上榜的其中 1 人更是擁有超過 2,000 億美元的財富。如今,美國前 20 位富豪的總財富額度達到了前所未有的水平 —— 相比去年增加了 5,000 億美元,達到 1.8 萬億美元,這比加拿大的 GDP 還要多。
5、Facebook:目前服務已恢復,沒有證據表明用戶數據因宕機而被泄露
5 日凌晨 0 點左右,Facebook 旗下多個平臺 ——Facebook、Instagram、WhatsApp、Messenger 等都遇到了服務器宕機問題,Instagram 和 WhatsApp 已經宕機超過六小時,刷新了自 2008 年最長宕機時長。
Facebook 在一篇博客中表示,“目前服務已恢復,我們正努力使其完全恢復正常運行,沒有證據表明用戶數據因宕機而被泄露。我們認為這次宕機的根本原因是錯誤的配置更改。”
6、谷歌云推出全新 N2 虛擬機:搭載第三代英特爾至強可擴展處理器
近日,谷歌云宣布即將發(fā)布搭載第三代英特爾至強可擴展處理器(代號為“Ice Lake”)的計算引擎 N2 機器系列公測版。
據介紹,N2 虛擬機可針對網絡和應用程序服務、企業(yè)應用程序、大中型數據庫、緩存、媒體轉碼流和游戲服務器等工作負載靈活調整,并為其提供均衡性能。此外,最新谷歌云 N2 機器系列利用第三代英特爾至強可擴展處理器中諸多 AI 及加密加速引擎來提升整體性能。
參考:開源中國、雷鋒網科技
IT之家、新浪科技等
在windows流行的時候,虛擬機保護是多人不敢碰的東西現在依然也是如此,pc的性能比移動端性能要高出不少,虛擬化和變異的代碼多到令人發(fā)指,因此在加密保護強度上要比移動端要強很多很多,為了移動端App更好的體驗(ANR率)移動端加密強度短時間內不會達到pc上的強度,隨著移動cpu性能越來越好相信加密強度會逐年加強。
早年興趣使然分析研究過windows端VMProtect、Safengine Shielden、Themida、VProtect、Enigma Protector等等虛擬機,最近發(fā)現國內流行的短視頻也有虛擬機加密同時也比較感興趣,便開始了我的分析之旅。
分析任何虛擬機必須要扣匯編指令級細節(jié)。
安卓誕生這么多年了至今沒有像windows端olldbg、x64dbg那樣友好的調試器,IDA PRO雖然自帶了安卓調試器總是沒有相像中的穩(wěn)定。lldb作為移動端iOS和android開發(fā)的御用調試器,帶源碼調試在開發(fā)環(huán)境中還算比較友好,而匯編級調試只能輸入命令行了,這是很多用慣了gui調試器的人接受不了的,但是個人發(fā)現lldb調試穩(wěn)定性出奇的好,功能上比IDA Pro的安卓調試器強大太多了。
靜態(tài)分析工具
IDA Pro
IDA Pro中的變量和標簽命名約定: 1. 無下線為精確的名字,已經非常了解代碼的功能和含義,例如:"xxx"。2. "_"單下劃線定義為代碼的功能和含義比較模糊,例如:"_xxx"。3. 雙下劃線為分析的代碼模糊不清,由于地址不好記憶但需要命名以區(qū)分,例如:"__xxx"。
動態(tài)分析工具
lldb
ARM參考文檔
《Arm Architecture Reference Manual》
《ARM Cortex-A Series Programmer's Guide for ARMv8-A》
其他工具
CyberChef
libEncryptor.so一共包含了三套虛擬機,三套虛擬機各自獨立并且代碼一模一樣,本文重點只分析 vm2虛擬機 。
虛擬機 指令編解碼 參考借鑒了arm64的一部分規(guī)則,并實現了自己的一套規(guī)則,在后面的解碼分析中會有很多和arm64解碼相似的地方。
另外虛擬機并沒有像VMProtect那樣將一條指令分割成多條"微指令"的方式,此虛擬機沒有把當前真實的上下文放到虛擬機上下文去模擬執(zhí)行,而是運行了一套自己單獨的上下文。
vm1
: 0xDA0
在JNI_OnLoad中被調用,該虛擬機起始位置在0xDA0,主要作用解密是注冊java native所需要的字符串和調用RegisterNatives進行注冊。
vm2
: 0x2AC4
虛擬機起始位置在0x2AC4,是java注冊的原生接口com.bytedance.frameworks.encryptor.EncryptorUtil.ttEncrypt(byte[] buff, int size),用來加密buff字節(jié)數組。
vm3
: 0x444
代碼位置在0x444,用于生成aes-128 cbc算法的key和iv。
在函數中調用虛擬機時會傳入一個指針數組類型參數變量,這是傳入到虛擬機入口的唯一參數。
void vm_entry(*int args)
c偽代碼來表示函數調用虛擬機入口
void ttEncrypt(char* buff, int buff_size) {
? ? int dst_size = size +0x76;
? ? char* pDstBuff = malloc(size +0x76);
? ? vm_entry(buff, size, pDstBuff, dst_size);
}
反匯編版本:
IDA Pro查看入口的cfg圖,復雜程序看似很難其實一點都不簡單,話說回來cfg看起來和ollvm的混淆平坦化非常相似,其實和ollvm混淆關系不大,只不過一部分的switch被拉平了,在了解調度邏輯后分析也不算復雜。
在代碼中依然能看到兩個ollvm swtich var變量,其作用沒有詳細分析,但整個cfg圖確定與ollvm關系不是很大,猜測開啟ollvm后性能會大幅下降影響app啟動速度了。
在進入虛擬機運行時前,在入口需要準備虛擬機所需的內存和參數。對虛擬機內存布局情況必須了解如指掌,這樣在動態(tài)和靜態(tài)分析時才不會迷失方向。
分配空間
入口首先會分配堆棧空間,真實堆棧空間中包含了參數占用、虛擬機運行時的上下文(context)和虛擬機堆棧的所使用的空間,
從上圖代碼中看出虛擬機堆棧起始位置位于sp+0x38,結束位置sp+0x4D8,大小4D8-0x38=4A0,虛擬機所有可用真實內存為0x4A0,其中上下文(context)使用0x150,剩余分配給虛擬機堆棧4A0-150=2C0。
vm_entry調用vm2_run時的堆棧內存布局情況:
SP | 類型 | 變量名 | 注釋 |
---|---|---|---|
0x0 | | | 未使用 |
0x8 | char * | pSrcBuffer | |
0x10 | int | srcSize | |
0x18 | char * | pDstBuffer | |
0x20 | int * | pDstBufferSize | |
0x28 | void * | pCall_register_trampoline | |
0x30 | void * | pVMMemoryEnd | 偏移0x510 |
0x38 | vmMemoryStart | 虛擬機運行時專用內存起始位置 | |
... | | | |
0x510 | | vmMemoryEnd | 虛擬機運行時內存結束位置 |
0x518 | | | |
0x520 | x28 | | |
0x528 | x19 | | |
0x530 | x29 | 上一個棧楨地址 | |
0x538 | x30 | | |
參數
在進入虛擬機正式執(zhí)行前,vm2_entry在進入虛擬機運行時前會對5個參數進行初始化。
pOpcode
:opcode指針指向虛擬機要執(zhí)行的代碼
pArgs
:傳入虛擬機的參數
pReserve
:未使用,用途暫時未知
pExternalFunc
:調用虛擬機外的函數列表,此列表是加密的。
pVmData
:一個結構體指針,結構體存儲了兩指針分別是函數跳板地址另一個是虛擬機內存結束地址,{0x0: pCallRegisterTrampoline, 0x8: pVMMemoryEnd}
vm2_run(void* pOpcode,??void* pArgs,??void* pReserve,??void* pExternalFunc, void* pVmData);
opcode
vm2的opcode起始地址在0xB090,共占用0x2D8字節(jié),每個opcode占用四個字節(jié)。
跳板函數
跳板函數是銜接適配虛擬機和外部函數以及參數的橋梁,它包含來自虛擬機傳遞的兩個參數, 參數1:x0是調用跳轉的一個外部函數地址,參數2:x1是外部函數需要使用的參數指針。
外部函數
外部地址函數目標地址是被簡單加密過的,在虛擬機指令中使用加法解密這8個地址,例如:0xDDD2D0(加密地址數據) + FF225870(key) = 0x2B40(目標地址)。
截圖中IDA Pro以默認0基址的地址,在調試時看到的地址數據是被代碼手動重定位后的數據。
vm2_run僅僅只分配了保存被調用者寄存器的堆棧內存空間,并沒有分配空閑的堆棧內存,在虛擬機真實開始之前會將傳遞進來的5個參數即0x-x4對虛擬機中的虛擬寄存器和真實專用寄存器進行初始化。
vm_run還初始化了解碼opcode的switch表,在初始化時發(fā)現一共初始化了6張switch表,當然在handler中還存在其他switch表,這么多表是如何來的?猜測編寫時只有1-2張表,在編譯器優(yōu)化后表就被分割成多塊了。
虛擬寄存器初始化 vm2_run初始化時會將傳遞的5個參數賦值給虛擬寄存器,其中包括PC和SP的值。 | 虛擬寄存器 | 參數 | 注釋 |
---|---|---|---|
x0 | | x0始終為0,XZR寄存器? | |
x4 | arg2: pArgs | | |
x5 | arg3: pReserve | | |
x6 | arg4: pExternalFunc | | |
x7 | arg4: pVmData->pCallRegisterTrampolineFunction | | |
x29(SP) | arg5: pVmData->pVmMemoryLimit - 0x150 | SP的初始值 | |
x31(LR) | 初始值為0,寄存器名不確定,vm退出時保存退出代碼編號 | | |
PC | arg1: pOpcode | | |
真實專用寄存器 虛擬機運行時使用了真實虛擬器,其中包括臨時寄存器和專用寄存器,臨時寄存器保存多種類型的值,而專用寄存器在虛擬機從開始到退出只保存一種指定類型數據或恒定不變。 opcode位域偽代碼: w12保存了4位32字節(jié)的opcode,在opcode首次解碼時,位域中的變量會放到真實寄存器。 位域偽代碼示例: w12[26] : 取第26位到放到入目標的26位 w12[26->0] : 取第26位并放入到目標的指定位 w12[27-26->1-0] : 取27位和26位放入到目標第1位和第0位 ` | `: 按位或 | 真實寄存器 | 注釋 |
---|---|---|---|
x0 | temp/pcode | | |
x1 | temp,保存[x19-0x20]的值某種流程控制 | | |
x2 | temp | | |
x3 | temp | | |
x4 | 虛擬寄存x4,初始化時保存pBufferInfo(x1),虛擬機中保存跳板函數地址 | | |
x5 | 虛擬寄存x5,初始化時保存數值為0,虛擬機運行時跳板函數的參數指針 | | |
x6 | ollvm混淆switch_var,初始值:0x400000b,這個應該是llvm混淆的switch var | | |
w7 | ollvm混淆switch_var,初始值:0x200fff,這個應該是llvm混淆的switch var | | |
w8 | w12[20-16]? ?operand1 Xt/Xm,det register index? | | |
w9 | w12[25-21]? ?operand2 Xn,src register index? | | |
w10 | w12[15->4] |??w12[14->3] | w12[13->2] | w12[12->1] | w12[31->0],5個位合并到低5位 | | |
w11 | w12[30->4] | w12[29->3] | w12[28->2] | w12[27-26->0-1],組成的低5位 | | |
w12 | 32位的opcode | | |
w13 | w12[31] | | |
w14 | w12[30] | | |
w15 | w12[29] | | |
w16 | w12[28] | | |
w17 | w12[27] | | |
w18 | w12[26] | | |
x19 | 虛擬機下文 負偏移指針 指向可使用內存最高上限的地址與vmMemoryLimit值相同,使用 負偏移 對上下文進行訪問。 | | |
x20 | call_register_trampoline 跳板地址 | | |
x21 | Context,上下文指針 | | |
x22 | switch_table7 | | |
x23 | switch_table6 | | |
w24 | 默認為1 | | |
x25 | switch_table5 | | |
x26 | switch_table3 | | |
x27 | switch_table_main | | |
x28 | switch_table2 | | |
x29(fp) | 未使用 | | |
x30(lr) | switch_table_4(3A) | | |
虛擬機context 虛擬機中也有專用寄存器,在調用外部函數時,其中 x4 虛擬機中保存外部函數地址, x5 虛擬機中保存參數指針, x25 虛擬機中調用外部函數時的跳板地址。另外虛擬寄存器和aarch64中的寄存器并不是一一對應的,在這里只是對每個虛擬寄存器啟了一個相應的名字方便理解和記憶。 | 負偏移 | 虛擬寄存器 | 注釋 |
---|---|---|---|
-0x150 | | 虛擬機堆棧SP初始位置 | |
-0x148 | | | |
-0x140 | | | |
-0x138 | pc | pc指針,真實寄存器x21中的值指向此地址,handler使用 | |
-0x130 | x0 | 初始化值:0,虛擬機中開始到結束始終為0,XZR寄存器? | |
-0x128 | x1 | | |
-0x120 | x2 | | |
-0x118 | x3 | | |
-0x110 | x4 | 初始化值:pBufferInfo,專用寄存器:虛擬機中調用外部函數時保存外部函數地址 | |
-0x108 | x5 | 初始化值: 0,專用寄存器:虛擬機中調用外部函數時保存參數指針 | |
-0x100 | x6 | 初始化值: vm2_external_func_list | |
-0xF8 | x7 | 初始化值: call_register_trampoline_function | |
-0xF0 | x8 | | |
-0xE8 | x9 | | |
-0xE0 | x10 | | |
-0xD8 | x11 | | |
-0xD0 | x12 | | |
-0xC8 | x13 | | |
-0xC0 | x14 | | |
-0xB8 | x15 | | |
-0xB0 | x16 | | |
-0xA8 | x17 | | |
-0xA0 | x18 | | |
-0x98 | x19 | | |
-0x90 | x20 | | |
-0x88 | x21 | | |
-0x80 | x22 | | |
-0x78 | x23 | | |
-0x70 | x24 | | |
-0x68 | x25 | 虛擬機中的專用寄存器:虛擬機中調用外部函數時的跳板地址 | |
-0x60 | x26 | | |
-0x58 | x27 | | |
-0x50 | x28 | | |
-0x48 | x29(SP) | 虛擬機堆棧SP, 初始化時指向-0x150 | |
-0x40 | x30 | | |
-0x38 | x31(LR) | 當值為0時退出虛擬機 | |
-0x30 | | | |
-0x28 | | | |
-0x20 | 0 | 流程控制狀態(tài)標記,值范圍0-3,0:正常執(zhí)行狀態(tài),2和3:程序中包含分支跳轉某種流程,3和1:call某個地址函數 | |
-0x18 | | new pc/call某個地址函數的地址/為0時退出虛擬機 | |
-0x10 | | LR,在調用調用函數結束后返回虛擬機地址 | |
-0x8 | 起始pc指針,一部分分支跳轉指令參考的起始基址,例如: 跳轉目標地址=pc起始地址+offset | | |
pOpcode取出4個字節(jié)的opcode并取得低5位解碼出op1。
# 初始化
LDP? ?? ?? ?? ? X20, X19, [X4]? ?? ?? ?? ???;x20:call_register_trampoline,0x19:pVmMemoryLimit
SUB? ?? ?? ?? ? X21, X19, #0x138? ?? ???;x19-0x138=x21計算出pc指針在負偏移指針的內存位置
STR? ?? ?? ?? ? X0, [X21]? ?? ?? ?? ?? ?? ?? ? ;保存pc到x21,此時x0和x21中的值相同并被關聯。
# 取opcode
LDR? ?? ?? ?? ?W12, [X0]? ?? ?? ?? ?? ?? ???;取出4字節(jié)opcode
AND? ?? ?? ???W10, W12, #0x3F? ?? ? ;opcode的低5位為op1
CMP? ?? ?? ???W10, #0x3F? ?? ?? ?? ?? ? ;op1的值最大只能小于63,說明op1只有0-63一共64個
B.HI? ?? ?? ?? ?next_pc? ?? ?? ?? ?? ?? ?? ? ;下一條指令
在opcode 首次解碼 時會解碼4個寄存器操作數,從中提取4個位域的字段,分別保存到 真實專用寄存器 w8、w9、w10、w11,在arm64指令集中當指令是MADD、MSUB、UMADDL、UMSUBL、SMADDL、SMADDL等會有4個寄存器操作數的情況,在這里同樣也是如此。
首次解碼時位域布局:w12[31,30-26,25-21,20-16,15-12,11-6,5-0]
w12[5-0:6] = op1
w12[11-6:6] = op2
w12[15-12:4]|[31:1] = Xm/Wm
w12[20-16:5] = Xt/Wt
w12[25-21:5] = Xn/Wn
w12[30-26:5] =Xa/Wa
w8
:? Rd(Xt/Wt)目標寄存器操作數,w12[20-16->5-0],取出16-20位保存最低位,5位可以表示0-31個寄存器。
w9
:? Rn(Xn/Wn)(第一個源寄存器操作數,w12[25-21->5-0],取出21-25位保存最低位,5位可以表示0-31個寄存器。
w10
: Rm(Xm/Wm)第二個源寄存器操作數,w12[15->4] | w12[14->3] | w12[13->2] | w12[12->1] | w12[31->0],5位可以表示0-31個寄存器。
w11
: Ra(Xa/Wa)第三個源寄存器操作數,w12[30->4] | w12[29->3] | w12[28->2] | w12[27-26->1-0],5位可以表示0-31個寄存器。
寄存器操作數偽代碼表示如下:
操作數大小
: X為64位操作數、W為32位操作數。
目標操作數
: d=destination register, t=target register。
源寄存器
:第一個源寄存器為n,第二個寄存器為m,第三個寄存器為a。
R
: 表示寄存器64或32位操作數,X是64位操作數、W代表32位操作數。
在高級語言中如果一個switch太多,在某些編譯器編譯優(yōu)化后出現在多張switch子表和子表的子表的情況,對于一些相同代碼的多個case會合并到一個case中再次switch分發(fā)的情況。
根據編譯器的優(yōu)化編譯的特性,在處理switch的case為了減少查找次數找到最終的case,在代碼中會經常看到大于(GT)、小于(LE)等分支跳轉,看到這個不要迷惑,這是編譯器優(yōu)化case的結果,這樣做的目的是減少查找次數使用了類似二分查找的算法,當看到B.EQ的跳轉目標和B.NE的下一條指令就是匹配到了case常量了。
在 首次解碼 后分發(fā)過程中通常還會有 二次解碼 ,除了MADD和MSUB等等指令有4個寄存器操作數,指令只需一般指令只有2-3個寄存器操作數,一條完整的arm64指令至少需要二個寄存器操作數,這里也同樣如此,當指令只有二個寄存器操作數時即一個目標操作數Xt和第一個源操作數Xn,其他的位域字段就會空閑下來,例如:op2、Xm、Xa,在二次解碼時這些空閑的位域字段原有的值就會覆蓋被再次利用組成其他尋址方式例如:shift、extend、imm等等。
在虛擬機指令op1的值是11(0xb)時,分發(fā)處理會解碼op2,解碼op2時Xt、Xn、Xm等操作數位置會發(fā)生變化,原有的x10/w10第三個源操作數在op2中變成目標操作數,第一個源操作數變成x8/w8,第二個源操作數變成x9/w9,各操作數的位域解碼方式不變,變的只是操作數角色。
Xt
:Rd(Xt/Wt)目標寄存器操作數(x10/w10),w12[15->4] | w12[14->3] | w12[13->2] | w12[12->1] | w12[31->0],5位可以表示0-31個寄存器。
Xn
: Rn(Xn/Wn)(第一個源寄存器操作數(x8/w8),w12[20-16->5-0],取出16-20位保存最低位,5位可以表示0-31個寄存器。
Xm
: Rm(Xm/Wm)第二個源寄存器操作數(x9/w9),w12[25-21->5-0],取出21-25位保存最低位,5位可以表示0-31個寄存器。
從op1的取值范圍為0-63一共64條指令,當op1是11時還有存在op2和op3的情況,由于虛擬機的handler太過龐大分析所有的指令太過耗時,我們目的是還原代碼邏輯,只需分析執(zhí)行過的handler,對于沒有執(zhí)行過的handler放棄分析。
在了解了op1解碼方式后,通過腳本得到執(zhí)行次數最多的指令并優(yōu)先分析:
import struct
pcode_start = 0xB090
pcode_size = 0x2D8
with open("libEncryptor.so", "rb") as f:
? ?? ???content = f.read()
bytes_code = content[pcode_start : pcode_start + pcode_size]
insts_sets = {}
for pc in range(0, pcode_size, 4):
? ? word = struct.unpack("<I", bytes_code[pc : pc + 4])??
? ? if len(word) > 0:
? ?? ???opcode = word[0]
? ?? ???op1 = opcode 0x3F
? ?? ???if insts_sets.get(op1, None) == None:
? ?? ?? ?? ? insts_sets[op1] = 1
? ?? ???else:
? ?? ?? ?? ? insts_sets[op1] += 1
sorted_dict = dict(sorted(insts_sets.items(), key=lambda item: item[1], reverse=True))
for inst, count in sorted_dict.items():
? ???print(f'op1: {inst}, count: {count}')
得到執(zhí)行次數最多的指令:
op1: 23, count: 56
op1: 11, count: 51
op1: 40, count: 38
op1: 21, count: 27
op1: 24, count: 2
op1: 12, count: 2
op1: 17, count: 2
op1: 48, count: 2
op1: 52, count: 1
op1: 7, count: 1
從python打印的結果來看23、11、40前面三個指令使用最為頻繁,因篇幅關系只分析這三條指令,其中op1值是11的還存在第二個操作碼op2,這里選擇op1=11 op2=12的指令進行分析。
取指
在循環(huán)的開始,首先判斷一個虛擬機狀態(tài)控制碼,指示了當前指令執(zhí)行完成是否進入某些分支跳轉指令流程,接著取出4個字節(jié)并判斷指令op1的合法性,如果不合法忽略當前指令直接跳轉B.HI next_pc執(zhí)行一條指令。
解碼
同樣首次解碼出四個寄存器操作數,w8=目標操作數Xt,w9=第一源操作數Xn,w10=第二個源操作數Xm,w11=第三個源操作數Xa。
分發(fā) 解碼2
既然op1=23,那么switch會跳轉到主分發(fā)表的case 23的位置,從圖中看出20,23,27,35,36,48,54,58多個case合并的到一個的case,可能是這些的代碼是相同的被合并到一個地方了。
2E50
: 代碼開頭4行代碼是判斷op1的范圍是否20-58之間來確定case的合法性,代碼中使用了首次解碼的寄存器w10并被直接重新賦值使用了,首次解碼時是第二個源寄存器Xm,說明此條指令可能沒有第二和第三個源寄存器。
2E60-2E8C
: 首次解碼的寄存器w11是第三個源寄存器,同樣被重新賦值給予新的含義了,此時有效的寄存器有w12(opcode)、w8(Xt)、w9(Xn)、x21(pContext上下文指針),這段代碼主要是做了三件事:
獲取Imm16:
獲取一個16位的立即數:w11 = w12[12-15] | w12[31->11] | w12[30->10] | w12[29->9] | w12[28->8] | w12[27->7] | w12[26->6] | w12[6-11->0-5],解碼過程的高級語言版本方便理解 (opcode 0xF000) |??(opcode >> 20 0xFC0) | (opcode >> 6 0x3F)
.text:0000000000002E60 060? ?? ?? ?? ?? ???UBFX? ?? ?? ?? ?W11, W12, #6, #6 ; w12[6-11->0-5]
.text:0000000000002E64 060? ?? ?? ?? ?? ???AND? ?? ?? ?? ? W12, W12, #0xF000 ; w12[12-15]
.text:0000000000002E68 060? ?? ?? ?? ?? ???BFXIL? ?? ?? ???W12, W18, #20, #12 ; w12[12-15] | w12[26->6]
.text:0000000000002E6C 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W12, W11 ; w12[12-15] | w12[26->6] | w12[6-11->0-5]
.text:0000000000002E78 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W17,LSR#20 ; w12[27->7]
.text:0000000000002E80 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W16,LSR#20 ; w12[28->8]
.text:0000000000002E84 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W15,LSR#20 ; w12[29->9]
.text:0000000000002E88 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W14,LSR#20 ; w12[30->10]
.text:0000000000002E8C 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W13,LSR#20 ; w12[31->11]
獲取源操作數Xn的值:
2E70
:x21是pContext指針,w9是第一個源寄存器是一個偏移索引值,x9 = x21 + w9 8得到相對的偏移植。
2E7C
:??從上下文中取出第一個寄存器的值,x9 = x21 + w9 8 + 8。
.text:0000000000002E70 060? ?? ?? ?? ?? ???ADD? ?? ?? ?? ? X9, X21, W9,UXTW#3
.text:0000000000002E7C 060? ?? ?? ?? ?? ???LDR? ?? ?? ?? ? X9, [X9,#8]
計算出一個偏移量:
第一個寄存器的值加上imm16擴展到32位的立即數得到一個偏移量。
2E94 060? ?? ?? ?? ?? ???ADD? ?? ?? ?? ? X9, X9, W11,SXTH ; w9=address,取出半字16立即數再相加
執(zhí)行
再次分發(fā)后來到case 23的最終目標地:
x9:此時是一個偏移量
w8:是一個目標寄存器,它的值是一個索引值
3380
:??x21是pContext指針,w8是目標源寄存器是一個偏移索引值,x9 = x21 + w9 8得到相對的偏移植。
3384
: 取出目標寄存器的值,x8 = x21 + w8 8 + 8。
3388
: 將目標寄存器存儲到偏移量地址中,[Xn + imm]=Xt。
.text:0000000000003380? ???case_23__STR_Xt_Mem_Xn_simm
.text:0000000000003380 060? ?? ?? ?? ?? ???ADD? ?? ?? ?? ? X8, X21, W8,UXTW#3 ; jumptable 0000000000002E98 case 23
.text:0000000000003384 060? ?? ?? ?? ?? ???LDR? ?? ?? ?? ? X8, [X8,#8]
.text:0000000000003388 060? ?? ?? ?? ?? ???STR? ?? ?? ?? ? X8, [X9]
準備下一條指令: pc指針地址加4
338C 060? ?? ?? ?? ?? ???B? ?? ?? ?? ?? ?next_pc
指令還原
儲存指令:STR ?? Xt, [Xn + imm16]
當imm為0時: STR? ? Xt, [Xn]
當op1值等于11時會解碼第二個或第三個操作碼。
取指
這里不再重復參考op: 23取指部分
解碼
這里不再重復參考op: 23解碼部分
分發(fā) 解碼2
11號case首先會提取出op2對op2進行再次switch case分發(fā)處理。
經過分發(fā)跳轉跳轉來到這里,op2的多個case 1,6,8,12,17,26,36,58也被合并到一個位置。
經過多次跳轉來吧op2=8的最終目的地case 8,上文提到過op2與op1的操作數位置不一樣,Xn(w10),Xn(w8),Xm(w9)
3D60
: 獲取上下文中的寄存器偏移
3D64
:取第二次源操作數值,x9=[x11+ w9 8]
3D68
:取第一次源操作數值,x8=[x11+ w8 8]
3D6C
:減法運算
3D70
:將結果寫入到目標寄存器,[x11+ w10 * 8] = x8
指令還原
SUB? ? Xt, Xn, Xm
取指
這里不再重復參考op: 23取指部分
解碼
這里不再重復參考op: 23解碼部分
分發(fā) 解碼2
在指令的開頭3條指令中第二個(w10)、第三個源寄存器(w11)的值被重新賦值并使用,說明指令中有效寄存器操作數只有Xt和Xn。
經過分析二次解碼和op: 23一模一樣,獲取立即數、獲取源操作數的值、獲取一個偏移值
獲取imm
opcode提取imm16位立即數:
w11 = w12[12-15] | w12[31->11] | w12[30->10] | w12[29->9] | w12[28->8] | w12[27->7] | w12[26->6] | w12[6-11->0-5]
.text:0000000000002D50 060? ?? ?? ?? ?? ???UBFX? ?? ?? ?? ?W11, W12, #6, #6 ; w12[6-11->0-5]
.text:0000000000002D54 060? ?? ?? ?? ?? ???AND? ?? ?? ?? ? W12, W12, #0xF000 ; w12[12-15]
.text:0000000000002D58 060? ?? ?? ?? ?? ???BFXIL? ?? ?? ???W12, W18, #20, #12 ; w12[12-15] | w12[26->6]
.text:0000000000002D5C 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W12, W11 ; w12[12-15] | w12[28->6] | w12[6-11->0-5]
.text:0000000000002D68 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W17,LSR#20 ; w12[27->7]
.text:0000000000002D70 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W16,LSR#20 ; w12[28->8]
.text:0000000000002D74 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W15,LSR#20 ; w12[29->9]
.text:0000000000002D78 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W14,LSR#20 ; w12[30->10]
.text:0000000000002D7C 060? ?? ?? ?? ?? ???ORR? ?? ?? ?? ? W11, W11, W13,LSR#20 ;
獲取源操作數的值
.text:0000000000002D60 060? ?? ?? ?? ?? ???ADD? ?? ?? ?? ? X9, X21, W9,UXTW#3
.text:0000000000002D6C 060? ?? ?? ?? ?? ???LDR? ?? ?? ?? ? X9, [X9,#8] ; << Xn
計算一個偏移量
.text:0000000000002D84 060? ?? ?? ?? ?? ???ADD? ?? ?? ?? ? X9, X9, W11,SXTH ; X9 = Xn + imm16
執(zhí)行
經過二次解碼分析得出目前有效的真實寄存器有:x9是源寄存器加上立即的一個偏移量,w8是目標操作數索引值。
3318
:偏移量的內存中取值
3A70
:計算目標寄存器在pContext的偏移地址,x8= x21 + w8 * 8
3A74
:將內存值放入目標寄存器
.text:0000000000003318 060? ?? ?? ?? ?? ???LDR? ?? ?? ?? ? X9, [X9]
.text:0000000000003A70 060? ?? ?? ?? ?? ???ADD? ?? ?? ?? ? X8, X21, W8,UXTW#3
.text:0000000000003A74 060? ?? ?? ?? ?? ???STR? ?? ?? ?? ? X9, [X8,#8]
指令還原
加載指令:LDR ?? Xt, [Xn + imm16]
當imm為0時:LDR ?? Xt, [Xn]
分析完取指、解碼、執(zhí)行后大體得到了一個模糊的虛擬機執(zhí)行框架,為了方便記憶和理解使用c偽代碼來描述。
使用腳本解析opcode的op1和op2打印所有需要分析的handler。
? ?? ?? ?? ?import struct
? ?? ?? ?? ?pcode_start = 0xB090
? ?? ?? ?? ?pcode_size = 0x2D8
? ?? ?? ?? ?with open("libEncryptor.so", "rb") as f:
? ?? ?? ?? ?? ?? ?? ?? ?? ? content = f.read()
? ?? ?? ?? ?bytes_code = content[pcode_start : pcode_start + pcode_size]
? ?? ?? ?? ?insts_op1_sets = {}
? ?? ?? ?? ?insts_op2_sets = {}
? ?? ?? ?? ?for pc in range(0, pcode_size, 4):
? ?? ?? ?? ?? ?? ???word = struct.unpack("<I", bytes_code[pc : pc + 4])??
? ?? ?? ?? ?? ?? ???if len(word) > 0:
? ?? ?? ?? ?? ?? ?? ?? ?? ? opcode = word[0]
? ?? ?? ?? ?? ?? ?? ?? ?? ? op1 = opcode 0x3F
? ?? ?? ?? ?? ?? ?? ?? ?? ? if op1 == 11:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?op2 = (opcode >> 6) 0x3F
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?if insts_op2_sets.get(op2, None) == None:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???insts_op2_sets[op2] = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???insts_op2_sets[op2] += 1
? ?? ?? ?? ?? ?? ?? ?? ?? ? else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?if insts_op1_sets.get(op1, None) == None:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???insts_op1_sets[op1] = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???insts_op1_sets[op1] += 1
? ?? ?? ?? ?print("op1指令統(tǒng)計:")
? ?? ?? ?? ?op1_sorted_dict = dict(sorted(insts_op1_sets.items(), key=lambda item: item[1], reverse=True))
? ?? ?? ?? ?for inst, count in op1_sorted_dict.items():
? ?? ?? ?? ?? ?? ?? ?print(f'op1: {inst}, count: {count}')
? ?? ?? ?? ?print("op2指令統(tǒng)計:")
? ?? ?? ?? ?op2_sorted_dict = dict(sorted(insts_op2_sets.items(), key=lambda item: item[1], reverse=True))
? ?? ?? ?? ?for inst, count in op2_sorted_dict.items():
? ?? ?? ?? ?? ?? ?? ?print(f'op1: 11, op2: {inst}, count: {count}')
一共打印出15個handler,好在需要分析還原的指令不多:
op1指令統(tǒng)計:
op1: 23, count: 56
op1: 40, count: 38
op1: 21, count: 27
op1: 24, count: 2
op1: 12, count: 2
op1: 17, count: 2
op1: 48, count: 2
op1: 52, count: 1
op1: 7, count: 1
op2指令統(tǒng)計:
op1: 11, op2: 7, count: 25
op1: 11, op2: 43, count: 14
op1: 11, op2: 12, count: 8
op1: 11, op2: 25, count: 2
op1: 11, op2: 39, count: 1
op1: 11, op2: 62, count: 1
在15個handler并還原后,現在重新編寫腳本 decode_opcode.py
解碼opcode,將打印還原的指令打印出偽匯編代碼并保存文件 ttencryptor.asm
和 generate_aes_key_iv.asm
。
? ?? ???def decode(pcode_start, pcode_size, liner_disasm = False):
? ?? ?? ?? ?? ? with open("libEncryptor.so", "rb") as f:
? ?? ?? ?? ?? ?? ?? ?? ?content = f.read()
? ?? ?? ?? ?? ? print(f"{'pc':^8} {'指令':^8}??{'op1':^8}??{'op2':^8}\t{'助記符':^30}")
? ?? ?? ?? ?? ? print(f"{'-'*8}??{'-'*8}??{'-'*8}??{'-'*8}??MOV\tx0, #0\t\t\t;x0始終為0,XZR寄存器?")
? ?? ?? ?? ?? ? print(f"{'-'*8}??{'-'*8}??{'-'*8}??{'-'*8}??MOV\tx4, pArgs\t\t;參數列表指針")
? ?? ?? ?? ?? ? print(f"{'-'*8}??{'-'*8}??{'-'*8}??{'-'*8}??MOV\tx5, #0")
? ?? ?? ?? ?? ? print(f"{'-'*8}??{'-'*8}??{'-'*8}??{'-'*8}??MOV\tx6, pfn_external_func_list\t\t;外部函數列表指針")
? ?? ?? ?? ?? ? print(f"{'-'*8}??{'-'*8}??{'-'*8}??{'-'*8}??MOV\tx7, pCallRegisterTrampolineFunction\t;保存跳轉函數地址")
? ?? ?? ?? ?? ? print(f"{'-'*8}??{'-'*8}??{'-'*8}??{'-'*8}??MOV\tx29, pVirualStackBottom;\t\t;虛擬機堆棧棧底")
? ?? ?? ?? ?? ? print(f"{'-'*8}??{'-'*8}??{'-'*8}??{'-'*8}??MOV\tlr, #0\t\t\t;x31=0")
? ?? ?? ?? ?? ? # 已解析的指令
? ?? ?? ?? ?? ? known_insns_op1 = {}? ?? ???# dcode_insns_status=1
? ?? ?? ?? ?? ? known_insns_op2 = {}? ?? ???# dcode_insns_status=2
? ?? ?? ?? ?? ? # 未解析的指令
? ?? ?? ?? ?? ? unknown_insts_op1 = set()? ?# dcode_insns_status=3
? ?? ?? ?? ?? ? unknown_insts_op2 = set()? ?# dcode_insns_status=4
? ?? ?? ?? ?? ? bytes_code = content[pcode_start : pcode_start + pcode_size]
? ?? ?? ?? ?? ? for pc in range(0, pcode_size, 4):
? ?? ?? ?? ?? ?? ?? ?? ?word = struct.unpack("<I", bytes_code[pc : pc + 4])??
? ?? ?? ?? ?? ?? ?? ?? ?asm = ""? ?? ?? ?
? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 0
? ?? ?? ?? ?? ?? ?? ?? ?if len(word) > 0:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???opcode = word[0]? ?? ?? ?? ?
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???op1 = get_op1(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???Xt = get_openand1(opcode)? ?# x8/w8
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???Xn = get_operand2(opcode)? ?# x9/w9? ?
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???Xm = get_operand3(opcode)? ?# x10/w10
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???X4 = get_operand4(opcode)? ?# x11/w11
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???match op1:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 11:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?Xt = get_operand3(opcode)? ?# x10/w10
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?Xn = get_openand1(opcode)? ?# x8/w8
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?Xm = get_operand2(opcode)? ?# x9/w9? ?? ?? ?? ?? ???
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?op2 = get_op2(opcode)? ?? ?? ?? ?? ?? ???
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?match op2:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case 7:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? dcode_insns_status=2
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tORR\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, {get_regsiter_name(Xm)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case 12:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? dcode_insns_status=2
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tADD\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, {get_regsiter_name(Xm)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case 25:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? dcode_insns_status=2
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? if X4 == 0:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tNOP\t\t\t\t;LSL\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, #{X4}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tLSL)\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, #{X4}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case 39:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? dcode_insns_status=2
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tCMP\t{get_regsiter_name(Xm)}, {get_regsiter_name(Xn)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? print(f"{' '*8}??{' '*8}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tCSET\t{get_regsiter_name(Xt)}, CC")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case 43:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? dcode_insns_status=2
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? if liner_disasm:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tBR\t{get_regsiter_name(Xm)}\t\t\t;LR={get_regsiter_name(Xt)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?asm = f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tBR\t{get_regsiter_name(Xm)}\t\t\t;LR={get_regsiter_name(Xt)}"
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?set_branch_control(asm)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case 62:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? dcode_insns_status=2
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? if liner_disasm:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tExitVm\t0\t\t\t;{get_regsiter_name(Xm)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?asm = f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\tExitVm\t0\t\t\t;{get_regsiter_name(Xm)}"
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?set_branch_control(asm)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???case _:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? dcode_insns_status=4
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{op2:02d}(0x{op2:02X})\t>> op2_xxx\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, {get_regsiter_name(Xm)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 7:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tORR\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, #{hex(imm16)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 12:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm26 = get_imm26(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?offset = imm26 * 4
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?if liner_disasm:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tB\t{hex(offset)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???asm = f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tB\t{hex(offset)}"
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???set_branch_control(asm)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 17:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tADD\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, #{hex(imm16)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 21:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tADD\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, #{hex(imm16)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 23:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tSTR\t{get_regsiter_name(Xt)}, [{get_regsiter_name(Xn)}, #{hex(imm16)}]")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 24:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?offset = pc + imm16 * 4 + 4
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?if liner_disasm:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tB.HS\t#{hex(offset)}\t\t\t;{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, ${hex(imm16 * 4)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???asm = f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tB.HS\t#{hex(offset)}\t\t\t;{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, ${hex(imm16 * 4)}"
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???set_branch_control(asm)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 40:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tLDR\t{get_regsiter_name(Xt)}, [{get_regsiter_name(Xn)}, #{hex(imm16)}]")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 48:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tSTR\t{get_regsiter_name(Xt, 32)}, [{get_regsiter_name(Xn)}, #{hex(imm16)}]")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case 52:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status = 1
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?imm16 = get_imm16(opcode, False)
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tMOVZ\t{get_regsiter_name(Xt, 32)}, #{hex(imm16)}, LSL#16")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{' '*8}??{' '*8}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\tSXTW\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xt, 32)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? case _:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?dcode_insns_status=3? ?? ?? ?? ?? ?? ?
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?print(f"{pc:08X}??{opcode:08X}??{opcode 0x3F:02d}(0x{opcode 0x3F:02X})??{'-'*8}\t>> op1_xxx\t{get_regsiter_name(Xt)}, {get_regsiter_name(Xn)}, {get_regsiter_name(Xm)}")
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???if not liner_disasm:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? branch_pipeline_process()
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???record_insns(dcode_insns_status, known_insns_op1, known_insns_op2, unknown_insts_op1, unknown_insts_op2, opcode)
? ?? ?? ?? ?? ?? ?? ?? ?else:
? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???print("error")? ?
? ?? ?? ?? ?? ? decode_statistics(known_insns_op1, known_insns_op2, unknown_insts_op1, unknown_insts_op2)
? ?? ???def main():
? ?? ?? ?? ?? ? # 解碼vm2
? ?? ?? ?? ?? ? pcode_start = 0xB090
? ?? ?? ?? ?? ? pcode_size = 0x2D8
? ?? ?? ?? ?? ? decode(pcode_start, pcode_size)
? ?? ?? ?? ?? ? # # 解碼vm3,獲取aes的key和iv
? ?? ?? ?? ?? ? pcode_start = 0xBDE0
? ?? ?? ?? ?? ? pcode_size = 0x1C8
? ?? ?? ?? ?? ? decode(pcode_start, pcode_size)
? ?? ?? ?? ?? ? # 解碼vm1,JNI_OnLoad
? ?? ?? ?? ?? ? pcode_start = 0x85C0
? ?? ?? ?? ?? ? pcode_size = 0xCC
? ?? ?? ?? ?? ? decode(pcode_start, pcode_size)
? ?? ???if __name__ == "__main__":
? ?? ?? ?? ?? ? main()
vm2還原的偽匯編代碼,經過分析主要做了這些事件:
保存寄存器環(huán)境
解密外部函數地址
生成隨機數
以隨機數為入參生成aes加密的key和iv
生成pSrcBuffer的hash
拼接數據hash+scrBuffer
aes加密拼接的數據
拼接加密結果: magic(0x6字節(jié)) + randNumber(0x20字節(jié)) + aesOut
恢復寄存器環(huán)境
由于vm2會調用vm3生成aes的key和iv,因此vm3的代碼也需要解析還原 generate_aes_key_iv.asm :
保存寄存器環(huán)境
解密外部函數地址
對隨機數生成hash
解密種子數據
對隨機數生成hash和種子數據再次生成hash
再次生成的hash數據中截取key和iv
恢復寄存器環(huán)境
這就是libEncryptor.so中ttEncrypt函數的加密算法了。
import secrets
import hashlib
from Crypto.Cipher import AES? ?# pip install pycryptodome
from Crypto.Util.Padding import pad, unpad
def generate_rand_number():
? ?? ???return secrets.token_bytes(32)
def decrypt_seeds():
? ?? ???key1 =??[0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB]
? ?? ???key1 += [0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB]
? ?? ???key1 += [0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E]
? ?? ???key1 += [0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25]
? ?? ???key2 =??[0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F]
? ?? ???key2 += [0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF]
? ?? ???key2 += [0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61]
? ?? ???key2 += [0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D]
? ?? ???results = bytearray()
? ?? ???for i in range(len(key1)):
? ?? ?? ?? ?? ? results.append(key1[i] ^ key2[i])
? ?? ???return results
def sha512(buff):
? ?? ???sha512_hash = hashlib.sha512()
? ?? ???sha512_hash.update(buff)
? ?? ???return sha512_hash.digest()
def get_aes_key_iv(rand_num):? ?
? ?? ???rand_num_hash = sha512(rand_num)? ?
? ?? ???seeds = decrypt_seeds()
? ?? ???data = rand_num_hash + seeds? ?
? ?? ???key_iv_hash = sha512(data)
? ?? ???print(f"rand_num:? ?? ?? ?? ???{rand_num.hex()}")
? ?? ???print(f"rand_num_hash:? ?? ?? ?{rand_num_hash.hex()}")
? ?? ???print(f"seeds:? ?? ?? ?? ?? ???{seeds.hex()}")? ?
? ?? ???print(f"rand_num_hash + seeds: {data.hex()}")
? ?? ???print(f"key_iv_hash:? ?? ?? ???{key_iv_hash.hex()}")
? ?? ???print(f"aes key:? ?? ?? ?? ?? ?{key_iv_hash[0:0x10].hex()}")
? ?? ???print(f"aes iv:? ?? ?? ?? ?? ? {key_iv_hash[0x10:0x20].hex()}")
? ?? ???return key_iv_hash[0:0x10], key_iv_hash[0x10:0x20]
def aes_128_cbc_encrypt(plaintext, key, iv):
? ?? ???# 創(chuàng)建一個 AES 加密器對象
? ?? ???cipher = AES.new(key, AES.MODE_CBC, iv)
? ?? ???# 添加填充并加密數據
? ?? ???padded_data = pad(plaintext, AES.block_size)
? ?? ???ciphertext = cipher.encrypt(padded_data)
? ?? ???return ciphertext
def get_magic():
? ?? ???return b"\x74\x63\x05\x10\x00\x00"
def ttEncrypt(buff):
? ?? ???magic = get_magic()
? ?? ???rand_number = generate_rand_number()
? ?? ???aes_key, aes_iv = get_aes_key_iv(rand_number)
? ?? ???buff_hash = sha512(buff)
? ?? ???aes_plaintext = buff_hash + buff
? ?? ???aes_ciphertext = aes_128_cbc_encrypt(aes_plaintext, aes_key, aes_iv)
? ?? ???print(f"plaintext:? ???{buff.hex()}")
? ?? ???print(f"rand_number:? ?{rand_number.hex()}")? ?
? ?? ???print(f"buff hash:? ???{buff_hash.hex()}")
? ?? ???print(f"aes_plaintext: {aes_plaintext.hex()}")
? ?? ???print(f"ciphertext:? ? {aes_ciphertext.hex()}")
? ?? ???return magic + rand_number + aes_ciphertext
def main():
? ?? ???buff = b'aabbccddeeffgg'? ?
? ?? ???result = ttEncrypt(buff)
? ?? ???print(f"ttEncrypt: {result.hex()}")
def test_aes():
? ?? ???buff =??b"\x61\x02\xbe\x54\xa6\x2a\x73\xe7\x65\xba\x38\xc9\x87\x34\x09\xbd" +\
? ?? ?? ?? ?? ?? ?? ?? ?b"\xeb\xb6\xb0\xd3\x7e\xa0\x60\x40\x3d\x0c\x26\xfe\xa5\xeb\xb6\xba" +\
? ?? ?? ?? ?? ?? ?? ?? ?b"\x5a\x0c\x7f\x36\xec\xb7\x58\xc7\x7e\x19\x37\x50\x5f\xa8\x5b\x4e" +\
? ?? ?? ?? ?? ?? ?? ?? ?b"\x77\xce\x82\x7a\x70\x09\xd2\x2b\x2f\xaf\xc4\x68\x00\xd7\xa9\xff" +\
? ?? ?? ?? ?? ?? ?? ?? ?b"\x62\x69\x61\x6e\x66\x65\x6e\x67"
? ?? ???aes_key = b"\xe8\xaf\x6e\x91\xde\x99\x7e\xf0\xfa\xfb\xcd\xbe\x97\x73\xb2\xc5"
? ?? ???aes_iv = b"\x03\x7e\xed\x97\x4e\x1e\xc5\x19\xdc\xc2\xb4\x35\x5b\x26\xf0\x1b"? ?
? ?? ???ciphertext = aes_128_cbc_encrypt(buff, aes_key, aes_iv)
? ?? ???print(f"ciphertext: {ciphertext.hex()}")
if __name__ == "__main__":
? ?? ???main()
? ?? ???# test_aes()
為了驗證算法是否有效,這里使用了dy模塊中的ttEncrypt算和tt的設備注冊代碼:
import secrets
import uuid
import time
import json
import hashlib
import gzip
import requests # pip install requests
import ttEncryptorUtil
# 注: 協(xié)議來自Tiktok
def UUID():
? ? return str(uuid.uuid4())
def md5(message):
? ? md5_hash = hashlib.md5()
? ? md5_hash.update(message)
? ? return md5_hash.hexdigest()
def get_timestamp_in_millisecond():
? ? return int(time.time() * 1000)
def get_timestamp_in_second():
? ? return int(time.time())
def generate_android_id():
? ? return secrets.token_bytes(8).hex()
def http_post(url, headers, payload):? ?
? ? response = requests.request("POST", url, headers=headers, data=payload)? ?
? ? return response.text
def gzip_compress(buff):
? ? return gzip.compress(buff)
def get_post_data(android_id, cdid, google_aid, clientudid):? ?
? ? openudid = android_id
? ? postDataObj = {
? ?? ???"magic_tag": "ss_app_log",
? ?? ???"header": {
? ?? ?? ?? ?"display_name": "TikTok",
? ?? ?? ?? ?"update_version_code": 2023205030,
? ?? ?? ?? ?"manifest_version_code": 2023205030,
? ?? ?? ?? ?"app_version_minor": "",
? ?? ?? ?? ?"aid": 1233,
? ?? ?? ?? ?"channel": "googleplay",
? ?? ?? ?? ?"package": "com.zhiliaoapp.musically",
? ?? ?? ?? ?"app_version": "32.5.3",
? ?? ?? ?? ?"version_code": 320503,
? ?? ?? ?? ?"sdk_version": "3.9.17-bugfix.9",
? ?? ?? ?? ?"sdk_target_version": 29,
? ?? ?? ?? ?"git_hash": "3e93151",
? ?? ?? ?? ?"os": "Android",
? ?? ?? ?? ?"os_version": "11",
? ?? ?? ?? ?"os_api": 30,
? ?? ?? ?? ?"device_model": "Pixel 2",
? ?? ?? ?? ?"device_brand": "google",
? ?? ?? ?? ?"device_manufacturer": "Google",
? ?? ?? ?? ?"cpu_abi": "arm64-v8a",
? ?? ?? ?? ?"release_build": "e7cd5de_20231207",
? ?? ?? ?? ?"density_dpi": 420,
? ?? ?? ?? ?"display_density": "mdpi",
? ?? ?? ?? ?"resolution": "1794x1080",
? ?? ?? ?? ?"language": "en",
? ?? ?? ?? ?"timezone": -5,
? ?? ?? ?? ?"access": "wifi",
? ?? ?? ?? ?"not_request_sender": 1,
? ?? ?? ?? ?"rom": "6934943",
? ?? ?? ?? ?"rom_version": "RP1A.201005.004.A1",
? ?? ?? ?? ?"cdid": cdid,
? ?? ?? ?? ?"sig_hash": "194326e82c84a639a52e5c023116f12a", # md5(packageInfo.signatures[0])
? ?? ?? ?? ?"gaid_limited": 0,
? ?? ?? ?? ?"google_aid": google_aid,
? ?? ?? ?? ?"openudid": openudid,
? ?? ?? ?? ?"clientudid": clientudid,
? ?? ?? ?? ?"tz_name": "America\/New_York",
? ?? ?? ?? ?"tz_offset": -18000,
? ?? ?? ?? ?"req_id": UUID(),
? ?? ?? ?? ?"device_platform": "android",
? ?? ?? ?? ?"custom": {
? ?? ?? ?? ?? ? "is_kids_mode": 0,
? ?? ?? ?? ?? ? "filter_warn": 0,
? ?? ?? ?? ?? ? "web_ua": "Mozilla\/5.0 (Linux; Android 11; Pixel 2 Build\/RP1A.201005.004.A1; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/116.0.0.0 Mobile Safari\/537.36",
? ?? ?? ?? ?? ? "user_period": 0,
? ?? ?? ?? ?? ? "screen_height_dp": 683,
? ?? ?? ?? ?? ? "user_mode": -1,
? ?? ?? ?? ?? ? "apk_last_update_time": 1702363135217,
? ?? ?? ?? ?? ? "screen_width_dp": 411
? ?? ?? ?? ?},
? ?? ?? ?? ?"apk_first_install_time": 1697783355395,
? ?? ?? ?? ?"is_system_app": 0,
? ?? ?? ?? ?"sdk_flavor": "global",
? ?? ?? ?? ?"guest_mode": 0
? ?? ???},
? ?? ???"_gen_time": get_timestamp_in_millisecond()
? ? }
? ? return gzip_compress(json.dumps(postDataObj).encode(encoding='utf-8'))
def get_headers(md5Hash):
? ? headers = {
? ?? ?? ?? ?'log-encode-type': 'gzip',
? ?? ?? ?? ?'x-tt-request-tag': 't=0;n=1',
? ?? ?? ?? ?'sdk-version': '2',
? ?? ?? ?? ?'X-SS-REQ-TICKET': f'{get_timestamp_in_millisecond()}',
? ?? ?? ?? ?'passport-sdk-version': '19',
? ?? ?? ?? ?'x-tt-dm-status': 'login=0;ct=1;rt=4',
? ?? ?? ?? ?'x-vc-bdturing-sdk-version': '2.3.4.i18n',
? ?? ?? ?? ?'Content-Type': 'application/octet-stream;tt-data=a',
? ?? ?? ?? ?'X-SS-STUB': md5Hash,
? ?? ?? ?? ?'Host': 'log-va.tiktokv.com'
? ?? ???}
? ? return headers
def get_device_register_url(openudid, cdid):
? ? url = 'https://log-va.tiktokv.com/service/2/device_register/?' + \
? ?? ?? ?? ?"tt_data=a" + \
? ?? ?? ?? ?"ac=wifi" + \
? ?? ?? ?? ?"channel=googleplay" + \
? ?? ?? ?? ?"aid=1233" + \
? ?? ?? ?? ?"app_name=musical_ly" + \
? ?? ?? ?? ?"version_code=320503" + \
? ?? ?? ?? ?"version_name=32.5.3" + \
? ?? ?? ?? ?"device_platform=android" + \
? ?? ?? ?? ?"os=android" + \
? ?? ?? ?? ?"ab_version=32.5.3" + \
? ?? ?? ?? ?"ssmix=a" + \
? ?? ?? ?? ?"device_type=Pixel+2" + \
? ?? ?? ?? ?"device_brand=google" + \
? ?? ?? ?? ?"language=en" + \
? ?? ?? ?? ?"os_api=30" + \
? ?? ?? ?? ?"os_version=11" + \
? ?? ?? ?? ?f"openudid={openudid}" + \
? ?? ?? ?? ?"manifest_version_code=2023205030" + \
? ?? ?? ?? ?"resolution=1080*1794" + \
? ?? ?? ?? ?"dpi=420" + \
? ?? ?? ?? ?"update_version_code=2023205030" + \
? ?? ?? ?? ?f"_rticket={get_timestamp_in_millisecond()}" + \
? ?? ?? ?? ?"is_pad=0" + \
? ?? ?? ?? ?"current_region=TW" + \
? ?? ?? ?? ?"app_type=normal" + \
? ?? ?? ?? ?"timezone_name=America%2FNew_York" + \
? ?? ?? ?? ?"residence=TW" + \
? ?? ?? ?? ?"app_language=en" + \
? ?? ?? ?? ?"ac2=wifi5g" + \
? ?? ?? ?? ?"uoo=0" + \
? ?? ?? ?? ?"op_region=TW" + \
? ?? ?? ?? ?"timezone_offset=-18000" + \
? ?? ?? ?? ?"build_number=32.5.3" + \
? ?? ?? ?? ?"host_abi=arm64-v8a" + \
? ?? ?? ?? ?"locale=en" + \
? ?? ?? ?? ?f"ts={get_timestamp_in_second()}" + \
? ?? ?? ?? ?f"cdid={cdid}"
? ? return url
def device_register():? ?
? ? android_id = generate_android_id()
? ? cdid = UUID()
? ? google_aid = UUID()
? ? clientudid = UUID()
? ? gzip_post_data = get_post_data(android_id, cdid, google_aid, clientudid)
? ? ttencrypt_post_data = ttEncryptorUtil.ttEncrypt(gzip_post_data)
? ? headers = get_headers(md5(ttencrypt_post_data))
? ? url = get_device_register_url(android_id, cdid)
? ? response = http_post(url, headers, ttencrypt_post_data)
? ? print(f"設備注冊結果:\n{response}")
if __name__ == "__main__":
? ? device_register()
結果:
設備注冊結果:
{"server_time":1719390270,"device_id":7384724147647088134,"install_id":7384724761467832069,"new_user":1,"device_id_str":"7384724147647088134","install_id_str":"7384724761467832069"}